modules/auxiliary/analyze/crack_databases.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::PasswordCracker
include Msf::Exploit::Deprecated
moved_from 'auxiliary/analyze/jtr_mssql_fast'
moved_from 'auxiliary/analyze/jtr_mysql_fast'
moved_from 'auxiliary/analyze/jtr_oracle_fast'
moved_from 'auxiliary/analyze/jtr_postgres_fast'
def initialize
super(
'Name' => 'Password Cracker: Databases',
'Description' => %Q{
This module uses John the Ripper or Hashcat to identify weak passwords that have been
acquired from the mssql_hashdump, mysql_hashdump, postgres_hashdump, or oracle_hashdump modules.
Passwords that have been successfully cracked are then saved as proper credentials.
Due to the complexity of some of the hash types, they can be very slow. Setting the
ITERATION_TIMEOUT is highly recommended.
},
'Author' =>
[
'theLightCosine',
'hdm',
'h00die' # hashcat integration
] ,
'License' => MSF_LICENSE, # JtR itself is GPLv2, but this wrapper is MSF (BSD)
'Actions' =>
[
['john', 'Description' => 'Use John the Ripper'],
['hashcat', 'Description' => 'Use Hashcat'],
],
'DefaultAction' => 'john',
)
register_options(
[
OptBool.new('MSSQL',[false, 'Include MSSQL hashes', true]),
OptBool.new('MYSQL',[false, 'Include MySQL hashes', true]),
OptBool.new('ORACLE',[false, 'Include Oracle hashes', true]),
OptBool.new('POSTGRES',[false, 'Include Postgres hashes', true]),
OptBool.new('INCREMENTAL',[false, 'Run in incremental mode', true]),
OptBool.new('WORDLIST',[false, 'Run in wordlist mode', true])
]
)
end
def show_command(cracker_instance)
return unless datastore['ShowCommand']
if action.name == 'john'
cmd = cracker_instance.john_crack_command
elsif action.name == 'hashcat'
cmd = cracker_instance.hashcat_crack_command
end
print_status(" Cracking Command: #{cmd.join(' ')}")
end
def print_results(tbl, cracked_hashes)
cracked_hashes.each do |row|
unless tbl.rows.include? row
tbl << row
end
end
tbl.to_s
end
def run
def process_crack(results, hashes, cred, hash_type, method)
return results if cred['core_id'].nil? # make sure we have good data
# make sure we dont add the same one again
if results.select {|r| r.first == cred['core_id']}.empty?
results << [cred['core_id'], hash_type, cred['username'], cred['password'], method]
end
create_cracked_credential( username: cred['username'], password: cred['password'], core_id: cred['core_id'])
results
end
def check_results(passwords, results, hash_type, hashes, method, cracker)
passwords.each do |password_line|
password_line.chomp!
next if password_line.blank?
fields = password_line.split(":")
# If we don't have an expected minimum number of fields, this is probably not a hash line
if action.name == 'john'
cred = {}
# we branch here since postgres (dynamic_1034) doesn't include the core_id
if ['dynamic_1034'].include? hash_type
next unless fields.count >=2
cred['username'] = fields.shift
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
cred['core_id'] = nil
# we now need to read the pot file to pull the original hash to match it back up successfully
pot = File.open(cracker.john_pot_file, 'rb')
pots = pot.read
pot.close
# here we combine un:hash and hash:password to make un:hash:password
combined = []
pots.each_line do |p|
next unless p.starts_with?('$dynamic_1034$')
hashes.each do |h|
next unless p.starts_with? "#{h['hash'].split(':')[1]}$HEX$"
cred['core_id'] = h['id']
break
end
end
else
#mysql*, mssql*, oracle*
next unless fields.count >=3
cred['username'] = fields.shift
cred['core_id'] = fields.pop
cred['password'] = fields.join(':') # Anything left must be the password. This accounts for passwords with semi-colons in it
end
results = process_crack(results, hashes, cred, hash_type, method)
elsif action.name == 'hashcat'
next unless fields.count >= 2
case hash_type
when 'dynamic_1034'
# for postgres we get 3 fields, hash:un:pass.
hash = fields.shift
username = fields.shift
password = fields.join(':')
when 'oracle11', 'raw-sha1,oracle'
hash = "#{fields.shift}#{fields.shift}" # we pull the first two fields, hash, and salt
password = fields.join(':')
else
hash = fields.shift
password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them
end
next if hash.include?("Hashfile '") && hash.include?("' on line ") # skip error lines
hashes.each do |h|
case hash_type
when 'mssql05','mssql12', 'mysql'
# mssql\d\d comes back as 0x then the rest uppercase, so we need to upcase everything to match correctly
next unless h['hash'].upcase == hash.upcase
when 'mssql'
# for whatever reason hashcat zeroes out part of the hash in --show.
# show: 0x0100a607ba7c0000000000000000000000000000000000000000b6d6261460d3f53b279cc6913ce747006a2e3254:FOO
# orig: 0x0100A607BA7C54A24D17B565C59F1743776A10250F581D482DA8B6D6261460D3F53B279CC6913CE747006A2E3254
hash_zero_format = "#{h['hash'][0..13]}#{'0'*40}#{h['hash'][54..hash.length]}".upcase
next unless hash_zero_format == hash.upcase
when 'mysql-sha1'
# add the * back to the beginning to match up and fix casing
next unless h['hash'] == "*#{hash}".upcase
when 'oracle11', 'raw-sha1,oracle'
next unless h['hash'].starts_with? "S:#{hash};".upcase
when 'oracle12c'
next unless h['hash'].include? ";T:#{hash}"
else
next unless h['hash'] == hash
end
cred = {'core_id' => h['id'],
'username' => h['un'],
'password' => password}
results = process_crack(results, hashes, cred, hash_type, method)
end
end
end
results
end
tbl = Rex::Text::Table.new(
'Header' => 'Cracked Hashes',
'Indent' => 1,
'Columns' => ['DB ID', 'Hash Type', 'Username', 'Cracked Password', 'Method']
)
# array of hashes in jtr_format in the db, converted to an OR combined regex
hashes_regex = []
if datastore['MSSQL']
hashes_regex << 'mssql'
hashes_regex << 'mssql05'
hashes_regex << 'mssql12'
end
if datastore['MYSQL']
hashes_regex << 'mysql'
hashes_regex << 'mysql-sha1'
end
if datastore['ORACLE']
# dynamic_1506 is oracle 11/12's H field, MD5.
# hashcat requires a format we dont have all the data for
# in the current dumper, so this is disabled in module and lib
if action.name == 'john'
hashes_regex << 'oracle'
hashes_regex << 'dynamic_1506'
end
hashes_regex << 'raw-sha1,oracle'
hashes_regex << 'oracle11'
hashes_regex << 'oracle12c'
end
if datastore['POSTGRES']
hashes_regex << 'dynamic_1034'
end
# check we actually have an action to perform
fail_with(Failure::BadConfig, 'Please enable at least one database type to crack') if hashes_regex.empty?
# array of arrays for cracked passwords.
# Inner array format: db_id, hash_type, username, password, method_of_crack
results = []
cracker = new_password_cracker(action.name)
# create the hash file first, so if there aren't any hashes we can quit early
# hashes is a reference list used by hashcat only
cracker.hash_path, hashes = hash_file(hashes_regex)
# generate our wordlist and close the file handle.
wordlist = wordlist_file
unless wordlist
print_error('This module cannot run without a database connected. Use db_connect to connect to a database.')
return
end
wordlist.close
print_status "Wordlist file written out to #{wordlist.path}"
cleanup_files = [cracker.hash_path, wordlist.path]
hashes_regex.each do |format|
# dupe our original cracker so we can safely change options between each run
cracker_instance = cracker.dup
cracker_instance.format = format
if action.name == 'john'
cracker_instance.fork = datastore['FORK']
end
# first check if anything has already been cracked so we don't report it incorrectly
print_status "Checking #{format} hashes already cracked..."
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Already Cracked/POT', cracker_instance)
vprint_good(print_results(tbl, results))
if action.name == 'john'
print_status "Cracking #{format} hashes in single mode..."
cracker_instance.mode_single(wordlist.path)
show_command cracker_instance
cracker_instance.crack do |line|
vprint_status line.chomp
end
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Single', cracker_instance)
vprint_good(print_results(tbl, results))
print_status "Cracking #{format} hashes in normal mode"
cracker_instance.mode_normal
show_command cracker_instance
cracker_instance.crack do |line|
vprint_status line.chomp
end
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Normal', cracker_instance)
vprint_good(print_results(tbl, results))
end
print_status "Cracking #{format} hashes in wordlist mode..."
if action.name == 'john'
# Turn on KoreLogic rules if the user asked for it
if datastore['KORELOGIC']
cracker_instance.rules = 'KoreLogicRules'
print_status "Applying KoreLogic ruleset..."
end
end
show_command cracker_instance
cracker_instance.crack do |line|
vprint_status line.chomp
end
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Wordlist', cracker_instance)
vprint_good(print_results(tbl, results))
if datastore['INCREMENTAL']
print_status "Cracking #{format} hashes in incremental mode..."
cracker_instance.mode_incremental
show_command cracker_instance
cracker_instance.crack do |line|
vprint_status line.chomp
end
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Incremental', cracker_instance)
vprint_good(print_results(tbl, results))
end
if datastore['WORDLIST']
print_status "Cracking #{format} hashes in wordlist mode..."
cracker_instance.mode_wordlist(wordlist.path)
# Turn on KoreLogic rules if the user asked for it
if action.name == 'john' && datastore['KORELOGIC']
cracker_instance.rules = 'KoreLogicRules'
print_status "Applying KoreLogic ruleset..."
end
show_command cracker_instance
cracker_instance.crack do |line|
vprint_status line.chomp
end
results = check_results(cracker_instance.each_cracked_password, results, format, hashes, 'Wordlist', cracker_instance)
vprint_good(print_results(tbl, results))
end
#give a final print of results
print_good(print_results(tbl, results))
end
if datastore['DeleteTempFiles']
cleanup_files.each do |f|
File.delete(f)
end
end
end
def hash_file(hashes_regex)
hashes = []
wrote_hash = false
hashlist = Rex::Quickfile.new("hashes_tmp")
# Convert names from JtR to db
hashes_regex = hashes_regex.join('|')
hashes_regex = hashes_regex.gsub('oracle', 'des|oracle')\
.gsub('dynamic_1506', 'raw-sha1|oracle11|oracle12c|dynamic_1506')\
.gsub('oracle11', 'raw-sha1|oracle11')\
.gsub('dynamic_1034', 'postgres|raw-md5')
regex = Regexp.new hashes_regex
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::NonreplayableHash').each do |core|
next unless core.private.jtr_format =~ regex
# only add hashes which havne't been cracked
next unless already_cracked_pass(core.private.data).nil?
if action.name == 'john'
hashlist.puts hash_to_jtr(core)
elsif action.name == 'hashcat'
# hashcat hash files dont include the ID to reference back to so we build an array to reference
hashes << {'hash' => core.private.data, 'un' => core.public.username, 'id' => core.id}
hashlist.puts hash_to_hashcat(core)
end
wrote_hash = true
end
if datastore['POSTGRES']
framework.db.creds(workspace: myworkspace, type: 'Metasploit::Credential::PostgresMD5').each do |core|
next unless core.private.jtr_format =~ regex
# only add hashes which havne't been cracked
next unless already_cracked_pass(core.private.data).nil?
if action.name == 'john'
# hashcat hash files dont include the ID to reference back to so we build an array to reference
# however, for postgres, john doesn't take an id either
hashes << {'hash' => hash_to_jtr(core), 'un' => core.public.username, 'id' => core.id}
hashlist.puts hash_to_jtr(core)
elsif action.name == 'hashcat'
# hashcat hash files dont include the ID to reference back to so we build an array to reference
hashes << {'hash' => core.private.data, 'un' => core.public.username, 'id' => core.id}
hashlist.puts hash_to_hashcat(core)
end
wrote_hash = true
end
end
hashlist.close
unless wrote_hash # check if we wrote anything and bail early if we didn't
hashlist.delete
fail_with Failure::NotFound, 'No applicable hashes in database to crack'
end
print_status "Hashes Written out to #{hashlist.path}"
return hashlist.path, hashes
end
end