/modules/auxiliary/unstable/duckduck_password.rb
Ruby | 138 lines | 103 code | 22 blank | 13 comment | 13 complexity | f191eb4d7925d04bbb694ee63f3a8ce5 MD5 | raw file
- ##
- # 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/
- ##
- # The most useful and interesting feature of this module is the way the
- # loot file gets appended to, rather than rewritten. Might be use for
- # this technique in other modules that gather loot.
- require 'msf/core'
- class Metasploit3 < Msf::Auxiliary
- include Msf::Exploit::Remote::HttpClient
- def initialize(info = {})
- super(update_info(info,
- 'Name' => 'DuckDuckGo Password Hash Search',
- 'Description' => %q{
- This module scrapes the DuckDuckGo search engine for appearances of the given hash.
- When a hash is mentioned on the Internet, it is usually accompanied by the plaintext
- version of the password. Good news for the attacker, bad news for the user.
- Note that this module almost certainly violates the Terms of Service of DuckDuckGo,
- which is a shame, and relegates this to a proof of concept trick until a combination
- of search engine, TOS, and a useful API surfaces.
- },
- 'Author' => [ 'todb' ],
- 'References' =>
- [
- [ 'URL', 'http://duckduckgo.com/' ] # I heart DDG and feel bad for abusing them :/
- ]
- ))
- register_options(
- [
- OptString.new('HASH', [ true, "The password hash to search for", "b963c57010f218edc2cc3c229b5e4d0f"]),
- OptBool.new('SSL', [ false, "Use SSL for hash searches", true]) # Just bringing this to the main opts list.
- ], self.class)
- deregister_options('RHOST', 'RPORT', 'VHOST', 'Proxies')
- end
- def cleanup
- datastore['RHOST'] = @old_rhost
- datastore['RPORT'] = @old_rport
- end
- # Save the original rhost/rport in case the user was exploiting something else
- def save_rhost
- @old_rhost = datastore['RHOST']
- @old_rport = datastore['RPORT']
- end
- def pw_hash
- datastore['HASH'].to_s
- end
- def run
- save_rhost()
- # Need to set this for send_request_cgi()
- datastore['RHOST'] = "duckduckgo.com"
- datastore['RPORT'] = datastore['SSL'] ? 443 : 80
- loot = ""
- uri = "/html/"
- res = send_request_cgi({
- 'method' => 'GET',
- 'uri' => "/html/",
- 'vars_get' => {"q" => pw_hash}
- }, 25)
- if res == nil or res.body == nil
- print_error("No response from DuckDuckGo.")
- return
- end
- ddg_res = find_ddg_response(res.body)
- if ddg_res
- print_good("DuckDuckGo's first listing for '#{pw_hash}' is at #{ddg_res}")
- report_hash_found(ddg_res)
- else
- print_status("No results for #{datastore['HASH']} were found via DuckDuckGo.")
- return
- end
- end
- # Instead of one loot file at a time, it's nicer to just append.
- def report_hash_found(url)
- return unless framework.db.active
- loot_header = "Hash,URL"
- loot_line = "\"#{pw_hash}\",\"#{url}\"\n"
- existing_loot = framework.db.loots.find_by_ltype("internet.hashes")
- if existing_loot
- append_to_loot(existing_loot,loot_line,url)
- else
- loot_file = [loot_header,loot_line].join("\n")
- p = store_loot("internet.hashes","text/plain",nil,loot_file,"internet_hashes.csv","Internet-Searchable Hashes")
- print_status("Saved hash and URL to #{p}")
- end
- end
- def append_to_loot(existing_loot,loot_line,url)
- dupe_hash = false
- p = existing_loot.path
- fh = ::File.open(p, "r+b")
- fh.each_line do |line|
- if line == loot_line
- dupe_hash = true
- break
- end
- end
- if dupe_hash
- print_status "Discarding duplicate hash '#{pw_hash}' found in #{existing_loot.path}"
- fh.close
- return
- end
- # fh.seek(fh.stat.size)
- fh.write loot_line
- fh.close
- existing_loot.updated_at = Time.now.utc
- existing_loot.save
- print_status("Appended hash and URL to #{p}")
- end
- def find_ddg_response(html)
- first_result = html.match(/<div[^>]+web\-result.*?This is the visible part/m)[0]
- return nil unless first_result
- url = first_result.match(/href=\x22(http.*)\x22/)[1] rescue nil
- return url
- end
- end