Skip to content

Diamond ticket: preserve KDC-issued PAC_REQUESTOR and PAC_ATTRIBUTES in -request mode#2201

Open
0xCZR1 wants to merge 1 commit into
fortra:masterfrom
0xCZR1:diamond-preserve-pac-requestor
Open

Diamond ticket: preserve KDC-issued PAC_REQUESTOR and PAC_ATTRIBUTES in -request mode#2201
0xCZR1 wants to merge 1 commit into
fortra:masterfrom
0xCZR1:diamond-preserve-pac-requestor

Conversation

@0xCZR1

@0xCZR1 0xCZR1 commented Jun 6, 2026

Copy link
Copy Markdown

Introduction

When using ticketer.py in diamond mode (-request without -impersonate), the script requests a real TGT from the KDC but then rebuilds the PAC from scratch via createBasicPac(). This discards the KDC-issued PAC_REQUESTOR (type 18) and PAC_ATTRIBUTES (type 17) structures and replaces them with fabricated versions via createRequestorInfoPac().

On fully patched KDCs with KB5008380/KB5020009 enforcement, this causes KDC_ERR_TGT_REVOKED (substatus 0x520) during cross-realm referral TGS-REQ. The KDC validates whether the PAC_REQUESTOR in the TGT matches what it originally issued. The fabricated version, while structurally valid, was never issued by the KDC and fails this validation.

This is specifically triggered during cross-realm operations (e.g., ExtraSid golden/diamond tickets targeting a parent domain from a child domain). Intra-realm usage may work because the same KDC that issued the TGT processes the TGS-REQ, but cross-realm referrals to a different KDC expose the mismatch.

Reproduction scenario

The patched ticketer.py forges a diamond ticket on Linux with -request and -extra-sid (Enterprise Admins SID from the parent domain). The resulting .ccache is converted to .kirbi and injected into a Windows SYSTEM session on a domain-joined host via LsaRegisterLogonProcess + KerbSubmitTicketMessage. Windows then handles the cross-realm referral natively using AES-256, bypassing the RC4 inter-realm trust incompatibility that breaks Linux-based cross-realm attacks.

Before (without patch): TGT injects successfully. klist.exe get cifs/PARENT-DC01.parent.domain.com triggers the cross-realm referral TGS-REQ. The KDC rejects it with substatus 0x520 (KDC_ERR_TGT_REVOKED) because the fabricated PAC_REQUESTOR does not match what the KDC originally issued. Windows LSA purges the TGT.

After (with patch): Same flow. The preserved PAC_REQUESTOR passes KDC validation. Cross-realm referral succeeds. Three tickets appear: the cross-realm TGT, the child-domain TGT, and the cifs/ service ticket for the parent DC. Forest root C$ is accessible.

Why diamond mode (not sapphire)

Sapphire tickets (-impersonate) cannot be used for cross-realm privilege escalation via ExtraSid for three reasons:

  1. No ExtraSid support. The -extra-sid logic in customizeTicket() is in the non-impersonate branch. Sapphire mode never processes ExtraSid.

  2. Protected Users blocks S4U2Self. Sapphire needs to impersonate a privileged user via S4U2Self+U2U to obtain their PAC. In hardened environments, all Domain Admins and Enterprise Admins are in Protected Users, which blocks S4U2Self delegation.

  3. Different purpose. Sapphire copies existing privilege from a real user's PAC (stealth). Diamond fabricates privilege by modifying group memberships and adding ExtraSid (escalation). When the target privilege only exists in a parent domain (Enterprise Admins) and every account holding it is protected, diamond with ExtraSid is the only viable approach.

This patch ensures diamond mode works against fully patched KDCs where KB5020009 enforcement rejects the fabricated PAC_REQUESTOR during cross-realm referral.

What this PR does

Adds _extractOriginalPacFields() method that:

  1. Decrypts the real TGT's EncTicketPart using the krbtgt key (already required for diamond mode)
  2. Walks the PAC buffer entries
  3. Preserves the KDC-issued PAC_REQUESTOR and PAC_ATTRIBUTES structures

After createBasicPac() rebuilds the PAC with modified group memberships and ExtraSid, the preserved originals overwrite the fabricated entries. The PAC signatures are then recomputed with the krbtgt key as usual.

This only applies to diamond mode (-request without -impersonate). Sapphire mode (-impersonate) is unaffected as it has its own PAC handling via S4U2Self+U2U.

Failure case without this patch

# Cross-realm diamond ticket with ExtraSid (child → parent)
ticketer.py -request -domain child.domain.com \
  -domain-sid S-1-5-21-... -user 'MACHINE$' \
  -hashes ':...' -nthash '...' -aesKey '...' \
  -extra-sid S-1-5-21-...-519 \
  -dc-ip 10.x.x.x 'MACHINE$'

# Inject into Windows SYSTEM session, request cross-realm service ticket:
# → KDC_ERR_TGT_REVOKED (0x520) during referral TGS-REQ
# → Windows LSA purges the TGT from cache
image

With this patch

[*] Requesting TGT to target domain to use as basis
[*]     Preserved original PAC_ATTRIBUTES from KDC
[*]     Preserved original PAC_REQUESTOR from KDC
[*] Customizing ticket for child.domain.com/MACHINE$
...
[*] Saving ticket in MACHINE$.ccache

# Cross-realm referral succeeds, service ticket obtained
image image

Testing

Tested against Windows Server 2022 DCs (build 20348) with KB5020009 enforcement enabled, in a multi-domain forest with RC4 inter-realm trust keys. Cross-realm ExtraSid diamond ticket with Enterprise Admins SID successfully obtained cross-realm service tickets after the patch. Without the patch, the same configuration failed with 0x520 on every attempt.

Notes

  • The -old-pac flag bypasses this entirely (no PAC_REQUESTOR/PAC_ATTRIBUTES at all)
  • Falls back gracefully to fabricated PAC_REQUESTOR if decryption fails (with a warning)
  • No new dependencies, no new CLI arguments

@anadrianmanrique anadrianmanrique added the in review This issue or pull request is being analyzed label Jun 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in review This issue or pull request is being analyzed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants