Path: blob/master/modules/auxiliary/analyze/crack_databases.rb
21537 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Auxiliary6include Msf::Auxiliary::PasswordCracker7include Msf::Exploit::Deprecated8moved_from 'auxiliary/analyze/jtr_mssql_fast'9moved_from 'auxiliary/analyze/jtr_mysql_fast'10moved_from 'auxiliary/analyze/jtr_oracle_fast'11moved_from 'auxiliary/analyze/jtr_postgres_fast'1213def initialize14super(15'Name' => 'Password Cracker: Databases',16'Description' => %(17This module uses John the Ripper or Hashcat to identify weak passwords that have been18acquired from the mssql_hashdump, mysql_hashdump, postgres_hashdump, or oracle_hashdump modules.19Passwords that have been successfully cracked are then saved as proper credentials.20Due to the complexity of some of the hash types, they can be very slow. Setting the21ITERATION_TIMEOUT is highly recommended.22MSSQL is 131, 132, and 1731 in hashcat.23MYSQL is 200, and 300 in hashcat.24ORACLE is 112, and 12300 in hashcat.25POSTGRES is 12 in hashcat.26),27'Author' => [28'theLightCosine',29'hdm',30'h00die' # hashcat integration31],32'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)33'Actions' => [34['john', { 'Description' => 'Use John the Ripper' }],35['hashcat', { 'Description' => 'Use Hashcat' }],36['auto', { 'Description' => 'Auto-selection of cracker' }]37],38'DefaultAction' => 'auto',39'Notes' => {40'Stability' => [CRASH_SAFE],41'SideEffects' => [],42'Reliability' => []43}44)4546register_options(47[48OptBool.new('MSSQL', [false, 'Include MSSQL hashes', true]),49OptBool.new('MYSQL', [false, 'Include MySQL hashes', true]),50OptBool.new('ORACLE', [false, 'Include Oracle hashes', true]),51OptBool.new('POSTGRES', [false, 'Include Postgres hashes', true]),52OptBool.new('INCREMENTAL', [false, 'Run in incremental mode', true]),53OptBool.new('WORDLIST', [false, 'Run in wordlist mode', true])54]55)56end5758def show_command(cracker_instance)59return unless datastore['ShowCommand']6061if @cracker_type == 'john'62cmd = cracker_instance.john_crack_command63elsif @cracker_type == 'hashcat'64cmd = cracker_instance.hashcat_crack_command65end66print_status(" Cracking Command: #{cmd.join(' ')}")67end6869def check_results(passwords, results, hash_type, method)70passwords.each do |password_line|71password_line.chomp!72next if password_line.blank?7374fields = password_line.split(':')75cred = { 'hash_type' => hash_type, 'method' => method }7677if @cracker_type == 'john'78next unless fields.count >= 37980cred['username'] = fields.shift81cred['core_id'] = fields.pop82cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it83elsif @cracker_type == 'hashcat'84next unless fields.count >= 28586cred['core_id'] = fields.shift87case hash_type88when 'dynamic_1034'89# for postgres we get 4 fields, id:hash:un:pass.90cred['hash'] = fields.shift91cred['username'] = fields.shift92when 'oracle11', 'raw-sha1,oracle'93cred['hash'] = "#{fields.shift}#{fields.shift}" # we pull the first two fields, hash and salt94else95cred['hash'] = fields.shift96end97cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it9899next if cred['core_id'].include?("Hashfile '") && cred['core_id'].include?("' on line ") # skip error lines100101# we don't have the username since we overloaded it with the core_id (since its a better fit for us)102# so we can now just go grab the username from the DB103cred['username'] = framework.db.creds(workspace: myworkspace, id: cred['core_id'])[0].public.username104end105results = process_cracker_results(results, cred)106end107108results109end110111def run112tbl = cracker_results_table113cracker = new_password_cracker(action.name)114if action.name == 'auto'115@cracker_type = cracker.get_type116else117@cracker_type = action.name118end119120# array of hashes in jtr_format in the db, converted to an OR combined regex121hash_types_to_crack = []122123if datastore['MSSQL']124hash_types_to_crack << 'mssql'125hash_types_to_crack << 'mssql05'126hash_types_to_crack << 'mssql12'127end128if datastore['MYSQL']129hash_types_to_crack << 'mysql'130hash_types_to_crack << 'mysql-sha1'131end132if datastore['ORACLE']133# dynamic_1506 is oracle 11/12's H field, MD5.134135# hashcat requires a format we dont have all the data for136# in the current dumper, so this is disabled in module and lib137if @cracker_type == 'john'138hash_types_to_crack << 'oracle'139hash_types_to_crack << 'dynamic_1506'140end141hash_types_to_crack << 'oracle11'142hash_types_to_crack << 'oracle12c'143end144if datastore['POSTGRES']145hash_types_to_crack << 'dynamic_1034'146end147148jobs_to_do = []149150# build our job list151hash_types_to_crack.each do |hash_type|152job = hash_job(hash_type, cracker.cracker)153if job.nil?154print_status("No #{hash_type} found to crack")155else156jobs_to_do << job157end158end159160# bail early of no jobs to do161if jobs_to_do.empty?162print_good("No uncracked password hashes found for: #{hash_types_to_crack.join(', ')}")163return164end165166# array of arrays for cracked passwords.167# Inner array format: db_id, hash_type, username, password, method_of_crack168results = []169170# generate our wordlist and close the file handle.171wordlist = wordlist_file172unless wordlist173print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')174return175end176177wordlist.close178print_status "Wordlist file written out to #{wordlist.path}"179180cleanup_files = [wordlist.path]181182jobs_to_do.each do |job|183format = job['type']184hash_file = Rex::Quickfile.new("hashes_#{job['type']}_")185hash_file.puts job['formatted_hashlist']186hash_file.close187cracker.hash_path = hash_file.path188cleanup_files << hash_file.path189190# dupe our original cracker so we can safely change options between each run191cracker_instance = cracker.dup192cracker_instance.format = format193194if @cracker_type == 'john'195cracker_instance.fork = datastore['FORK']196end197198# first check if anything has already been cracked so we don't report it incorrectly199print_status "Checking #{format} hashes already cracked..."200results = check_results(cracker_instance.each_cracked_password, results, format, 'Already Cracked/POT')201vprint_good(append_results(tbl, results)) unless results.empty?202job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list203next if job['cred_ids_left_to_crack'].empty?204205if @cracker_type == 'john'206print_status "Cracking #{format} hashes in single mode..."207cracker_instance.mode_single(wordlist.path)208show_command cracker_instance209cracker_instance.crack do |line|210vprint_status line.chomp211end212results = check_results(cracker_instance.each_cracked_password, results, format, 'Single')213vprint_good(append_results(tbl, results)) unless results.empty?214job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list215next if job['cred_ids_left_to_crack'].empty?216217print_status "Cracking #{format} hashes in normal mode..."218cracker_instance.mode_normal219show_command cracker_instance220cracker_instance.crack do |line|221vprint_status line.chomp222end223results = check_results(cracker_instance.each_cracked_password, results, format, 'Normal')224vprint_good(append_results(tbl, results)) unless results.empty?225job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list226next if job['cred_ids_left_to_crack'].empty?227end228229if datastore['INCREMENTAL']230print_status "Cracking #{format} hashes in incremental mode..."231cracker_instance.mode_incremental232show_command cracker_instance233cracker_instance.crack do |line|234vprint_status line.chomp235end236results = check_results(cracker_instance.each_cracked_password, results, format, 'Incremental')237vprint_good(append_results(tbl, results)) unless results.empty?238job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list239next if job['cred_ids_left_to_crack'].empty?240end241242if datastore['WORDLIST']243print_status "Cracking #{format} hashes in wordlist mode..."244cracker_instance.mode_wordlist(wordlist.path)245# Turn on KoreLogic rules if the user asked for it246if @cracker_type == 'john' && datastore['KORELOGIC']247cracker_instance.rules = 'KoreLogicRules'248print_status 'Applying KoreLogic ruleset...'249end250show_command cracker_instance251cracker_instance.crack do |line|252vprint_status line.chomp253end254255results = check_results(cracker_instance.each_cracked_password, results, format, 'Wordlist')256vprint_good(append_results(tbl, results)) unless results.empty?257job['cred_ids_left_to_crack'] = job['cred_ids_left_to_crack'] - results.map { |i| i[0].to_i } # remove cracked hashes from the hash list258next if job['cred_ids_left_to_crack'].empty?259end260261# give a final print of results262print_good(append_results(tbl, results))263end264if datastore['DeleteTempFiles']265cleanup_files.each do |f|266File.delete(f)267end268end269end270end271272273