From ff0f5c4b00b5a64dfca7c35f3e4b6b30900ff929 Mon Sep 17 00:00:00 2001 From: Julien Rische Date: Tue, 9 Jan 2024 19:20:26 +0100 Subject: [PATCH] Add support for RFC8009 etypes in getST --- examples/getST.py | 275 ++++++++++++++++++++++-------------- impacket/krb5/kerberosv5.py | 62 +++++--- 2 files changed, 214 insertions(+), 123 deletions(-) diff --git a/examples/getST.py b/examples/getST.py index fc49527ffe..bd97790fed 100755 --- a/examples/getST.py +++ b/examples/getST.py @@ -8,7 +8,7 @@ # for more information. # # Description: -# Given a password, hash, aesKey or TGT in ccache, it will request a Service Ticket and save it as ccache +# Given a password, hash, aesKey, aesSha2Key or TGT in ccache, it will request a Service Ticket and save it as ccache # If the account has constrained delegation (with protocol transition) privileges you will be able to use # the -impersonate switch to request the ticket on behalf other user (it will use S4U2Self/S4U2Proxy to # request the ticket.) @@ -54,16 +54,32 @@ from impacket.examples import logger from impacket.examples.utils import parse_credentials from impacket.krb5 import constants -from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_FOR_USER_ENC, \ +from impacket.krb5.asn1 import AP_REQ, AS_REP, TGS_REQ, Authenticator, TGS_REP, seq_set, seq_set_iter, PA_S4U_X509_USER, S4UUserID, \ Ticket as TicketAsn1, EncTGSRepPart, PA_PAC_OPTIONS, EncTicketPart from impacket.krb5.ccache import CCache -from impacket.krb5.crypto import Key, _enctype_table, _HMACMD5, _AES256_SHA1_CTS, Enctype +from impacket.krb5.crypto import Key, _enctype_table, make_checksum, string_to_key, Enctype from impacket.krb5.constants import TicketFlags, encodeFlags from impacket.krb5.kerberosv5 import getKerberosTGS, getKerberosTGT, sendReceive from impacket.krb5.types import Principal, KerberosTime, Ticket from impacket.ntlm import compute_nthash from impacket.winregistry import hexdump +def cksumtype_for_enctype(enctype): + options = { + Enctype.DES_MD4: constants.ChecksumTypes.rsa_md4_des.value, + Enctype.DES_MD5: constants.ChecksumTypes.rsa_md5_des.value, + Enctype.DES3: constants.ChecksumTypes.hmac_sha1_des3_kd.value, + Enctype.RC4: constants.ChecksumTypes.hmac_md5.value, + Enctype.AES128_SHA1: constants.ChecksumTypes.hmac_sha1_96_aes128.value, + Enctype.AES256_SHA1: constants.ChecksumTypes.hmac_sha1_96_aes256.value, + Enctype.AES128_SHA256: constants.ChecksumTypes.hmac_sha256_128_aes128.value, + Enctype.AES256_SHA384: constants.ChecksumTypes.hmac_sha384_192_aes256.value + } + + if enctype not in options: + raise ValueError('No checksum type for enctype %d' % enctype) + return options[enctype] + class GETST: def __init__(self, target, password, domain, options): @@ -73,6 +89,7 @@ def __init__(self, target, password, domain, options): self.__lmhash = '' self.__nthash = '' self.__aesKey = options.aesKey + self.__aesSha2Key = options.aesSha2Key self.__options = options self.__kdcHost = options.dc_ip self.__force_forwardable = options.force_forwardable @@ -88,7 +105,7 @@ def saveTicket(self, ticket, sessionKey): ccache.fromTGS(ticket, sessionKey, sessionKey) ccache.saveFile(self.__saveFileName + '.ccache') - def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost, additional_ticket_path): + def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, aesSha2Key, kdcHost, additional_ticket_path): if not os.path.isfile(additional_ticket_path): logging.error("Ticket %s doesn't exist" % additional_ticket_path) exit(0) @@ -118,6 +135,11 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey aesKey = unhexlify(aesKey) except TypeError: pass + if isinstance(aesSha2Key, str): + try: + aesSha2Key = unhexlify(aesSha2Key) + except TypeError: + pass # Compute NTHash and AESKey if they're not provided in arguments if self.__password != '' and self.__domain != '' and self.__user != '': @@ -128,10 +150,16 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey print(hexlify(nthash).decode()) if not aesKey: salt = self.__domain.upper() + self.__user - aesKey = _AES256_SHA1_CTS.string_to_key(self.__password, salt, params=None).contents + aesSha2Key = string_to_key(Enctype.AES256_SHA1, self.__password, salt, params=None).contents if logging.getLogger().level == logging.DEBUG: logging.debug('AESKey') print(hexlify(aesKey).decode()) + if not aesSha2Key: + salt = self.__domain.upper() + self.__user + aesSha2Key = string_to_key(Enctype.AES256_SHA384, self.__password, salt, params=None).contents + if logging.getLogger().level == logging.DEBUG: + logging.debug('AESSHA2Key') + print(hexlify(aesSha2Key).decode()) # Get the encrypted ticket returned in the TGS. It's encrypted with one of our keys cipherText = tgs['ticket']['enc-part']['cipher'] @@ -141,8 +169,10 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey newCipher = _enctype_table[int(tgs['ticket']['enc-part']['etype'])] if newCipher.enctype == Enctype.RC4: key = Key(newCipher.enctype, nthash) - else: + elif newCipher.enctype in (Enctype.AES128_SHA1, Enctype.AES256_SHA1): key = Key(newCipher.enctype, aesKey) + else: + key = Key(newCipher.enctype, aesSha2Key) # Decrypt and decode the ticket # Key Usage 2 @@ -266,10 +296,7 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey reqBody['nonce'] = random.getrandbits(31) seq_set_iter(reqBody, 'etype', ( - int(constants.EncryptionTypes.rc4_hmac.value), - int(constants.EncryptionTypes.des3_cbc_sha1_kd.value), - int(constants.EncryptionTypes.des_cbc_md5.value), - int(cipher.enctype) + int(cipher.enctype), ) ) message = encoder.encode(tgsReq) @@ -295,12 +322,21 @@ def doS4U2ProxyWithAdditionalTicket(self, tgt, cipher, oldSessionKey, sessionKey return r, cipher, sessionKey, newSessionKey - def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost): + def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, aesSha2Key, kdcHost): decodedTGT = decoder.decode(tgt, asn1Spec=AS_REP())[0] # Extract the ticket from the TGT ticket = Ticket() ticket.from_asn1(decodedTGT['ticket']) + nonce = random.getrandbits(31) + + tgsReq = TGS_REQ() + + tgsReq['pvno'] = 5 + tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) + + tgsReq['padata'] = noValue + apReq = AP_REQ() apReq['pvno'] = 5 apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) @@ -309,6 +345,27 @@ def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost) apReq['ap-options'] = constants.encodeFlags(opts) seq_set(apReq, 'ticket', ticket.to_asn1) + reqBody = seq_set(tgsReq, 'req-body') + + opts = list() + opts.append(constants.KDCOptions.forwardable.value) + opts.append(constants.KDCOptions.renewable.value) + opts.append(constants.KDCOptions.canonicalize.value) + + reqBody['kdc-options'] = constants.encodeFlags(opts) + + serverName = Principal(self.__user, type=constants.PrincipalNameType.NT_UNKNOWN.value) + + seq_set(reqBody, 'sname', serverName.components_to_asn1) + reqBody['realm'] = str(decodedTGT['crealm']) + + now = datetime.datetime.utcnow() + datetime.timedelta(days=1) + + reqBody['till'] = KerberosTime.to_asn1(now) + reqBody['nonce'] = nonce + seq_set_iter(reqBody, 'etype', + (int(cipher.enctype),)) + authenticator = Authenticator() authenticator['authenticator-vno'] = 5 authenticator['crealm'] = str(decodedTGT['crealm']) @@ -318,6 +375,19 @@ def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost) seq_set(authenticator, 'cname', clientName.components_to_asn1) + reqBodyEncCksumType = cksumtype_for_enctype(cipher.enctype) + + reqBodyEnc = encoder.encode(reqBody)[2:] + reqBodyEncCksum = make_checksum(reqBodyEncCksumType, sessionKey, 6, reqBodyEnc) + + if logging.getLogger().level == logging.DEBUG: + logging.debug('KDC-REQ-BODY checksum') + hexdump(reqBodyEncCksum) + + authenticator['cksum'] = noValue + authenticator['cksum']['cksumtype'] = int(reqBodyEncCksumType) + authenticator['cksum']['checksum'] = reqBodyEncCksum + now = datetime.datetime.utcnow() authenticator['cusec'] = now.microsecond authenticator['ctime'] = KerberosTime.to_asn1(now) @@ -341,12 +411,6 @@ def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost) encodedApReq = encoder.encode(apReq) - tgsReq = TGS_REQ() - - tgsReq['pvno'] = 5 - tgsReq['msg-type'] = int(constants.ApplicationTagNumbers.TGS_REQ.value) - - tgsReq['padata'] = noValue tgsReq['padata'][0] = noValue tgsReq['padata'][0]['padata-type'] = int(constants.PreAuthenticationDataTypes.PA_TGS_REQ.value) tgsReq['padata'][0]['padata-value'] = encodedApReq @@ -356,61 +420,34 @@ def doS4U(self, tgt, cipher, oldSessionKey, sessionKey, nthash, aesKey, kdcHost) # identified to the KDC by the user's name and realm. clientName = Principal(self.__options.impersonate, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - S4UByteArray = struct.pack('= 0: - if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None): + if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None) and not aesSha2Key: from impacket.ntlm import compute_lmhash, compute_nthash lmhash = compute_lmhash(password) nthash = compute_nthash(password) - return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, kdcHost, requestPAC) + return getKerberosTGT(clientName, password, domain, lmhash, nthash, aesKey, aesSha2Key, kdcHost, requestPAC) raise @@ -532,7 +553,7 @@ def getKerberosType3(cipher, sessionKey, auth_data): return cipher, sessionKey2, resp.getData() -def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT = None, TGS = None, targetName='', +def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', aesSha2Key='', TGT = None, TGS = None, targetName='', kdcHost = None, useCache = True): # Convert to binary form, just in case we're receiving strings @@ -551,6 +572,11 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT aesKey = unhexlify(aesKey) except TypeError: pass + if isinstance(aesSha2Key, str): + try: + aesSha2Key = unhexlify(aesSha2Key) + except TypeError: + pass targetName = 'host/%s' % targetName if TGT is None and TGS is None: @@ -563,14 +589,14 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT if TGT is None: if TGS is None: try: - tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) + tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, aesSha2Key, kdcHost) except KerberosError as e: if e.getErrorCode() == constants.ErrorCodes.KDC_ERR_ETYPE_NOSUPP.value: # We might face this if the target does not support AES # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. - if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None) and TGT is None and TGS is None: + if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None) and not aesSha2Key and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) @@ -597,7 +623,7 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT # So, if that's the case we'll force using RC4 by converting # the password to lm/nt hashes and hope for the best. If that's already # done, byebye. - if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None) and TGT is None and TGS is None: + if lmhash == b'' and nthash == b'' and (aesKey == b'' or aesKey is None) and not aesSha2Key and TGT is None and TGS is None: from impacket.ntlm import compute_lmhash, compute_nthash LOG.debug('Got KDC_ERR_ETYPE_NOSUPP, fallback to RC4') lmhash = compute_lmhash(password) @@ -647,10 +673,14 @@ def getKerberosType1(username, password, domain, lmhash, nthash, aesKey='', TGT authenticator['cksum'] = noValue - authenticator['cksum']['cksumtype'] = 0x8003 - chkField = CheckSumField() - chkField['Lgth'] = 16 + if aesSha2Key: + authenticator['cksum']['cksumtype'] = constants.ChecksumTypes.hmac_sha384_192_aes256 + chkField['Lgth'] = 192 // 8 + else: + authenticator['cksum']['cksumtype'] = 0x8003 + chkField['Lgth'] = 16 + chkField['Flags'] = GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE #chkField['Flags'] = GSS_C_INTEG_FLAG | GSS_C_SEQUENCE_FLAG | GSS_C_REPLAY_FLAG | GSS_C_MUTUAL_FLAG | GSS_C_DCE_STYLE