Path: blob/master/modules/auxiliary/analyze/crack_webapps.rb
76950 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::PasswordCracker78def initialize9super(10'Name' => 'Password Cracker: Webapps',11'Description' => %(12This module uses John the Ripper or Hashcat to identify weak passwords that have been13acquired from various web applications.14Atlassian uses PBKDF2-HMAC-SHA1 which is 12001 in hashcat.15PHPass uses phpass which is 400 in hashcat.16Mediawiki is MD5 based and is 3711 in hashcat.17Apache Superset, some Flask and Werkzeug apps is pbkdf2-sha256 and is 10900 in hashcat18),19'Author' => [20'h00die'21],22'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)23'Actions' => [24['john', { 'Description' => 'Use John the Ripper' }],25['hashcat', { 'Description' => 'Use Hashcat' }],26['auto', { 'Description' => 'Auto-selection of cracker' }]27],28'DefaultAction' => 'auto',29'Notes' => {30'Stability' => [CRASH_SAFE],31'SideEffects' => [],32'Reliability' => []33}34)3536register_options(37[38OptBool.new('ATLASSIAN', [false, 'Include Atlassian hashes', true]),39OptBool.new('MEDIAWIKI', [false, 'Include MediaWiki hashes', true]),40OptBool.new('PHPASS', [false, 'Include Wordpress/PHPass, Joomla, phpBB3 hashes', true]),41OptBool.new('PBKDF2', [false, 'Apache Superset, some Flask and Werkzeug apps hashes', true]),42OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),43OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])44]45)46end4748def show_command(cracker_instance)49return unless datastore['ShowCommand']5051if @cracker_type == 'john'52cmd = cracker_instance.john_crack_command53elsif @cracker_type == 'hashcat'54cmd = cracker_instance.hashcat_crack_command55end56print_status(" Cracking Command: #{cmd.join(' ')}")57end5859def check_results(passwords, results, hash_type, method)60passwords.each do |password_line|61password_line.chomp!62next if password_line.blank?6364fields = password_line.split(':')65cred = { 'hash_type' => hash_type, 'method' => method }66# If we don't have an expected minimum number of fields, this is probably not a hash line67if @cracker_type == 'john'68next unless fields.count >= 36970cred['username'] = fields.shift71cred['core_id'] = fields.pop72cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it73elsif @cracker_type == 'hashcat'74next unless fields.count >= 27576cred['core_id'] = fields.shift77next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines7879case hash_type80when 'pbkdf2-sha256'81# hashcat format: cred_id:sha256:iterations:b64salt:b64hash:password824.times { fields.shift } # skip sha256, iterations, b64salt, b64hash83else84cred['hash'] = fields.shift85end86cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it8788# we don't have the username since we overloaded it with the core_id (since its a better fit for us)89# so we can now just go grab the username from the DB90cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username91end92results = process_cracker_results(results, cred)93end9495results96end9798def run99tbl = cracker_results_table100cracker = new_password_cracker(action.name)101if action.name == 'auto'102@cracker_type = cracker.get_type103else104@cracker_type = action.name105end106107hash_types_to_crack = []108hash_types_to_crack << 'PBKDF2-HMAC-SHA1' if datastore['ATLASSIAN']109hash_types_to_crack << 'phpass' if datastore['PHPASS']110hash_types_to_crack << 'mediawiki' if datastore['MEDIAWIKI']111hash_types_to_crack << 'pbkdf2-sha256' if datastore['PBKDF2']112jobs_to_do = []113114# build our job list115hash_types_to_crack.each do |hash_type|116job = hash_job(hash_type, @cracker_type)117if job.nil?118print_status("No #{hash_type} found to crack")119else120jobs_to_do << job121end122end123124# bail early of no jobs to do125if jobs_to_do.empty?126print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")127return128end129130# array of arrays for cracked passwords.131# Inner array format: db_id, hash_type, username, password, method_of_crack132results = []133134# generate our wordlist and close the file handle.135wordlist = wordlist_file136unless wordlist137print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')138return139end140141wordlist.close142print_status "Wordlist file written out to #{wordlist.path}"143144cleanup_files = [wordlist.path]145146jobs_to_do.each do |job|147format = job['type']148hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")149hash_file.puts job['formatted_hashlist']150hash_file.close151cracker.hash_path = hash_file.path152cleanup_files << hash_file.path153# dupe our original cracker so we can safely change options between each run154cracker_instance = cracker.dup155cracker_instance.format = format156if @cracker_type == 'john'157cracker_instance.fork = datastore['FORK']158end159160# first check if anything has already been cracked so we don't report it incorrectly161print_status "Checking #{format} hashes already cracked..."162results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')163vprint_good(append_results(tbl, results)) unless results.empty?164job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list165next if job['cred_ids_left_to_crack'].empty?166167if @cracker_type == 'john'168print_status "Cracking #{format} hashes in single mode..."169cracker_instance.mode_single(wordlist.path)170show_command cracker_instance171cracker_instance.crack do |line|172vprint_status line.chomp173end174results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')175vprint_good(append_results(tbl, results)) unless results.empty?176job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list177next if job['cred_ids_left_to_crack'].empty?178179print_status "Cracking #{format} hashes in normal mode..."180cracker_instance.mode_normal181show_command cracker_instance182cracker_instance.crack do |line|183vprint_status line.chomp184end185results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')186vprint_good(append_results(tbl, results)) unless results.empty?187job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list188next if job['cred_ids_left_to_crack'].empty?189end190191if datastore['INCREMENTAL']192print_status "Cracking #{format} hashes in incremental mode..."193cracker_instance.mode_incremental194show_command cracker_instance195cracker_instance.crack do |line|196vprint_status line.chomp197end198results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')199vprint_good(append_results(tbl, results)) unless results.empty?200job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list201next if job['cred_ids_left_to_crack'].empty?202end203204if datastore['WORDLIST']205print_status "Cracking #{format} hashes in wordlist mode..."206cracker_instance.mode_wordlist(wordlist.path)207# Turn on KoreLogic rules if the user asked for it208if @cracker_type == 'john' && datastore['KORELOGIC']209cracker_instance.rules = 'KoreLogicRules'210print_status 'Applying KoreLogic ruleset...'211end212show_command cracker_instance213cracker_instance.crack do |line|214vprint_status line.chomp215end216217results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')218vprint_good(append_results(tbl, results)) unless results.empty?219job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list220next if job['cred_ids_left_to_crack'].empty?221end222223# give a final print of results224print_good(append_results(tbl, results))225end226if datastore['DeleteTempFiles']227cleanup_files.each do |f|228File.delete(f)229end230end231end232end233234235