Skip to content

Commit c8974fb

Browse files
committed
Fix sapphire ticket generation when using ccache (-k) authentication
Three bugs prevented sapphire tickets from working with the -k (ccache) flow: 1. oldSessionKey was never assigned in the ccache code path, causing _extract_reply_ticket_times to fail with an unbound variable error. When loading from ccache there is no key exchange, so oldSessionKey equals sessionKey. 2. _extract_reply_ticket_times and the cipher-validation checks tried to read the enc-part etype from the ccache-sourced AS_REP structure, which may contain a stale/placeholder etype (e.g. DES etype 1) that is absent from impacket's _enctype_table, raising a KeyError. These checks are unnecessary for the -k path — the real cipher negotiation happens during S4U2Self+U2U — so skip them entirely. 3. saveTicket loaded the existing KRB5CCNAME ccache and appended the new ticket, carrying over the original TGT (e.g. DomainA\userA). Downstream tools then picked the first matching krbtgt credential instead of the sapphire ticket, causing cross-realm service tickets to be issued for the wrong principal. Always create a fresh ccache so the saved file contains only the forged ticket. Tested with sapphire ticket generation using a ccache TGT, followed by cross-realm TGS referral (DomainA → forest root → DomainB) to obtain cifs/ and host/ service tickets under the impersonated identity.
1 parent 176ffc3 commit c8974fb

1 file changed

Lines changed: 46 additions & 46 deletions

File tree

examples/ticketer.py

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ def createBasicTicket(self):
415415

416416
cipher = _enctype_table[creds['key']['keytype']]
417417
sessionKey = Key(creds['key']['keytype'], creds['key']['keyvalue'])
418+
oldSessionKey = sessionKey
418419

419420
# If we have the krbtgt AES key, decrypt the ticket and extract domain info from PAC
420421
if self.__options.aesKey and (not self.__options.domain_sid or not self.__options.user_id):
@@ -501,48 +502,52 @@ def createBasicTicket(self):
501502
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, self.__domain, None, tgt, cipher,
502503
sessionKey)
503504
kdcRep = decoder.decode(tgs, asn1Spec=TGS_REP())[0]
504-
self.__requested_ticket_times = self._extract_reply_ticket_times(kdcRep, oldSessionKey)
505+
506+
if not self.__options.k:
507+
self.__requested_ticket_times = self._extract_reply_ticket_times(kdcRep, oldSessionKey)
505508

506509
# Let's check we have all the necessary data based on the ciphers used. Boring checks
507-
ticketCipher = int(kdcRep['ticket']['enc-part']['etype'])
508-
encPartCipher = int(kdcRep['enc-part']['etype'])
509-
510-
if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \
511-
self.__options.nthash is None:
512-
logging.critical('rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. '
513-
'Can\'t continue ( or try running again w/o the -request option)')
514-
return None, None
515-
516-
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
517-
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
518-
self.__options.aesKey is None:
519-
logging.critical(
520-
'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
521-
'Can\'t continue (or try running again w/o the -request option)')
522-
return None, None
523-
524-
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
525-
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
526-
self.__options.aesKey is not None and len(self.__options.aesKey) > 32:
527-
logging.critical(
528-
'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. '
529-
'Can\'t continue (or try running again w/o the -request option)')
530-
return None, None
531-
532-
if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
533-
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and self.__options.aesKey is None:
534-
logging.critical(
535-
'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
536-
'Can\'t continue (or try running again w/o the -request option)')
537-
return None, None
538-
539-
if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
540-
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \
541-
self.__options.aesKey is not None and len(self.__options.aesKey) < 64:
542-
logging.critical(
543-
'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. '
544-
'Can\'t continue')
545-
return None, None
510+
# Skip these checks for ccache-sourced TGTs since the etype fields may not reflect the actual cipher
511+
if not self.__options.k:
512+
ticketCipher = int(kdcRep['ticket']['enc-part']['etype'])
513+
encPartCipher = int(kdcRep['enc-part']['etype'])
514+
515+
if (ticketCipher == EncryptionTypes.rc4_hmac.value or encPartCipher == EncryptionTypes.rc4_hmac.value) and \
516+
self.__options.nthash is None:
517+
logging.critical('rc4_hmac is used in this ticket and you haven\'t specified the -nthash parameter. '
518+
'Can\'t continue ( or try running again w/o the -request option)')
519+
return None, None
520+
521+
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
522+
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
523+
self.__options.aesKey is None:
524+
logging.critical(
525+
'aes128_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
526+
'Can\'t continue (or try running again w/o the -request option)')
527+
return None, None
528+
529+
if (ticketCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value or
530+
encPartCipher == EncryptionTypes.aes128_cts_hmac_sha1_96.value) and \
531+
self.__options.aesKey is not None and len(self.__options.aesKey) > 32:
532+
logging.critical(
533+
'aes128_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes128. '
534+
'Can\'t continue (or try running again w/o the -request option)')
535+
return None, None
536+
537+
if (ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
538+
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and self.__options.aesKey is None:
539+
logging.critical(
540+
'aes256_cts_hmac_sha1_96 is used in this ticket and you haven\'t specified the -aesKey parameter. '
541+
'Can\'t continue (or try running again w/o the -request option)')
542+
return None, None
543+
544+
if ( ticketCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value or
545+
encPartCipher == EncryptionTypes.aes256_cts_hmac_sha1_96.value) and \
546+
self.__options.aesKey is not None and len(self.__options.aesKey) < 64:
547+
logging.critical(
548+
'aes256_cts_hmac_sha1_96 is used in this ticket and the -aesKey you specified is not aes256. '
549+
'Can\'t continue')
550+
return None, None
546551
kdcRep['cname']['name-type'] = PrincipalNameType.NT_PRINCIPAL.value
547552
kdcRep['cname']['name-string'] = noValue
548553
kdcRep['cname']['name-string'][0] = self.__options.impersonate or self.__target
@@ -1109,12 +1114,7 @@ def signEncryptTicket(self, kdcRep, encASorTGSRepPart, encTicketPart, pacInfos):
11091114
def saveTicket(self, ticket, sessionKey):
11101115
logging.info('Saving/Updating ticket in %s' % (self.__target.replace('/', '.') + '.ccache'))
11111116
from impacket.krb5.ccache import CCache
1112-
from os import getenv, path
1113-
krb5 = getenv('KRB5CCNAME')
1114-
if krb5 and path.isfile(krb5):
1115-
ccache = CCache.loadFile(krb5)
1116-
else:
1117-
ccache = CCache()
1117+
ccache = CCache()
11181118

11191119
if self.__server == self.__domain:
11201120
ccache.fromTGT(ticket, sessionKey, sessionKey)

0 commit comments

Comments
 (0)