Merge pull request #86 from rapid7/feature/MSP-9719/postgres_crack

Feature/msp 9719/postgres crack

MSP-9719 #land
bug/bundler_fix
Trevor Rosen 2014-06-25 09:19:55 -05:00
commit f5ea6db604
3 changed files with 163 additions and 11 deletions

View File

@ -52,16 +52,8 @@ class Metasploit3 < Msf::Auxiliary
print_status line.chomp
end
print_status "Cracking #{format} hashes in incremental mode (All4)..."
cracker_instance.rules = nil
cracker_instance.wordlist = nil
cracker_instance.incremental = 'All4'
cracker_instance.crack do |line|
print_status line.chomp
end
print_status "Cracking #{format} hashes in incremental mode (Digits5)..."
cracker_instance.incremental = 'Digits5'
print_status "Cracking #{format} hashes in incremental mode (Digits)..."
cracker_instance.incremental = 'Digits'
cracker_instance.crack do |line|
print_status line.chomp
end

View File

@ -0,0 +1,120 @@
##
# This module requires Metasploit: http//metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/auxiliary/jtr'
class Metasploit3 < Msf::Auxiliary
#Included to grab the john.pot and use some utiltiy functions
include Msf::Auxiliary::JohnTheRipper
def initialize
super(
'Name' => 'John the Ripper Postgres SQL Password Cracker',
'Description' => %Q{
This module uses John the Ripper to attempt to crack Postgres password
hashes, gathered by the postgres_hashdump module. It is slower than some of the other
JtR modules because it has to do some wordlist manipulation to properly handle postgres'
format.
},
'Author' => ['theLightCosine'],
'License' => MSF_LICENSE
)
end
def run
@username_set = Set.new
cracker = new_john_cracker
hash_list = hash_file
#generate our wordlist and close the file handle
wordlist = wordlist_file
wordlist.close
print_status "Wordlist file written out to #{wordlist.path}"
cracker.wordlist = wordlist.path
cracker.hash_path = hash_list
['raw-md5'].each do |format|
cracker_instance = cracker.dup
cracker_instance.format = format
print_status "Cracking #{format} hashes in normal wordlist mode..."
cracker_instance.crack do |line|
print_status line.chomp
end
print_status "Cracking #{format} hashes in single mode..."
cracker_instance.rules = 'single'
cracker_instance.crack do |line|
print_status line.chomp
end
print_status "Cracking #{format} hashes in incremental mode (Digits)..."
cracker_instance.incremental = 'Digits'
cracker_instance.crack do |line|
print_status line.chomp
end
print_status "Cracked Passwords this run:"
cracker_instance.each_cracked_password 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
next unless fields.count >=3
username = fields.shift
core_id = fields.pop
password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them
# Postgres hashes always prepend the username to the password before hashing. So we strip the username back off here.
password.gsub!(/^#{username}/,'')
print_good "#{username}:#{password}:#{core_id}"
create_cracked_credential( username: username, password: password, core_id: core_id)
end
end
end
# Override the mixin method to add prependers
def wordlist_file
return nil unless framework.db.active
wordlist = Metasploit::Framework::JtR::Wordlist.new(
prependers: @username_set,
custom_wordlist: datastore['CUSTOM_WORDLIST'],
mutate: datastore['MUTATE'],
use_creds: datastore['USE_CREDS'],
use_db_info: datastore['USE_DB_INFO'],
use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'],
use_hostnames: datastore['USE_HOSTNAMES'],
use_common_root: datastore['USE_ROOT_WORDS'],
workspace: myworkspace
)
wordlist.to_file
end
def hash_file
hashlist = Rex::Quickfile.new("hashes_tmp")
Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'raw-md5,postgres').each do |hash|
hash.cores.each do |core|
user = core.public.username
@username_set << user
hash_string = "#{hash.data}"
id = core.id
hashlist.puts "#{user}:#{hash_string}:#{id}:"
end
end
hashlist.close
print_status "Hashes Written out to #{hashlist.path}"
hashlist.path
end
end

View File

@ -35,12 +35,42 @@ class Metasploit3 < Msf::Auxiliary
#Query the Postgres Shadow table for username and password hashes and report them
res = postgres_query('SELECT usename, passwd FROM pg_shadow',false)
service_data = {
address: ip,
port: rport,
service_name: 'postgres',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
private_data: datastore['PASSWORD'],
private_type: :password,
username: datastore['USERNAME'],
realm_key: Metasploit::Credential::Realm::Key::POSTGRESQL_DATABASE,
realm_value: datastore['DATABASE']
}
credential_data.merge!(service_data)
#Error handling routine here, borrowed heavily from todb
case res.keys[0]
when :conn_error
print_error("A Connection Error occured")
return
when :sql_error
# We know the credentials worked but something else went wrong
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
create_credential_login(login_data)
case res[:sql_error]
when /^C42501/
print_error "#{datastore['RHOST']}:#{datastore['RPORT']} Postgres - Insufficient permissions."
@ -50,6 +80,16 @@ class Metasploit3 < Msf::Auxiliary
return
end
when :complete
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Credential::Login::Status::SUCCESSFUL
}
login_data.merge!(service_data)
# We know the credentials worked and have admin access because we got the hashes
login_data[:access_level] = 'Admin'
create_credential_login(login_data)
print_status("Query appears to have run successfully")
end
@ -70,7 +110,7 @@ class Metasploit3 < Msf::Auxiliary
credential_data = {
origin_type: :service,
jtr_format: 'raw-md5',
jtr_format: 'raw-md5,postgres',
module_fullname: self.fullname,
private_type: :nonreplayable_hash
}