Overview
Theimpacket.krb5.ccache module implements the Kerberos credential cache file format used to store Kerberos tickets (TGT and TGS) for single sign-on. The implementation supports:
- Reading/writing ccache files (versions 3 and 4)
- Extracting tickets from cache
- Converting between ccache and ticket formats
- Converting to/from Kirbi format (Mimikatz)
- Parsing credentials from environment variables
Module Location
from impacket.krb5.ccache import CCache
impacket/krb5/ccache.py
CCache Class
Main class for credential cache operations.class CCache:
def __init__(self, data=None)
@classmethod
def loadFile(cls, fileName)
@classmethod
def loadKirbiFile(cls, fileName)
@classmethod
def parseFile(cls, domain='', username='', target='')
def saveFile(self, fileName)
def saveKirbiFile(self, fileName)
def getCredential(self, server, anySPN=True)
def fromTGT(self, tgt, oldSessionKey, sessionKey)
def fromTGS(self, tgs, oldSessionKey, sessionKey)
def fromKRBCRED(self, encodedKrbCred)
def toKRBCRED(self)
File Format
CCache File Structure
[2 bytes] file_format_version (0x0504)
[2 bytes] headerlen
[headerlen bytes] headers (version 4 only)
[variable] principal
[variable] credentials...
Version Support
- Version 3: Basic format without headers
- Version 4: Includes header section (recommended)
ccache = CCache()
ccache.MiniHeader()['file_format_version'] = 0x0504 # Version 4
Loading Credentials
Load from File
Load ccache from file path:from impacket.krb5.ccache import CCache
# Load ccache file
ccache = CCache.loadFile('/tmp/krb5cc_1000')
if ccache:
print(f"Principal: {ccache.principal.prettyPrint()}")
print(f"Credentials: {len(ccache.credentials)}")
Load from Environment
Automatically use KRB5CCNAME environment variable:import os
# Set environment variable
os.environ['KRB5CCNAME'] = '/tmp/krb5cc_1000'
# Parse credentials
domain, username, TGT, TGS = CCache.parseFile(
domain='DOMAIN.LOCAL',
username='user',
target='cifs/server.domain.local'
)
if TGT:
print("TGT found in cache")
print(f"Session key: {TGT['sessionKey']}")
print(f"Cipher: {TGT['cipher']}")
if TGS:
print("TGS found for target")
Load from Binary Data
Parse ccache from bytes:with open('/tmp/krb5cc_1000', 'rb') as f:
data = f.read()
ccache = CCache(data)
Saving Credentials
Save to File
ccache = CCache()
# ... populate credentials ...
ccache.saveFile('/tmp/krb5cc_new')
Create from TGT
Convert AS-REP to ccache:from impacket.krb5.kerberosv5 import getKerberosTGT
from impacket.krb5.ccache import CCache
from impacket.krb5.types import Principal
from impacket.krb5 import constants
# Get TGT
clientName = Principal('user', type=constants.PrincipalNameType.NT_PRINCIPAL.value)
tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(
clientName,
'password',
'DOMAIN.LOCAL',
lmhash=b'',
nthash=b''
)
# Create ccache
ccache = CCache()
ccache.fromTGT(tgt, oldSessionKey, sessionKey)
ccache.saveFile('/tmp/krb5cc_new')
Create from TGS
Convert TGS-REP to ccache:from impacket.krb5.kerberosv5 import getKerberosTGS
# Get TGS
serverName = Principal('cifs/server.domain.local',
type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(
serverName,
'DOMAIN.LOCAL',
'dc.domain.local',
tgt,
cipher,
tgtSessionKey
)
# Add to existing ccache
ccache.fromTGS(tgs, oldSessionKey, sessionKey)
ccache.saveFile('/tmp/krb5cc_updated')
Credential Structure
Credential Class
Represents a single cached ticket:class Credential:
# Access credential fields
credential['client'] # Client principal
credential['server'] # Service principal
credential['key'] # Session key
credential['time'] # Ticket times
credential['is_skey'] # Is session key?
credential['tktflags'] # Ticket flags
credential.ticket # Encoded ticket
Credential Fields
# Client and server principals
print(f"Client: {cred['client'].prettyPrint()}")
print(f"Server: {cred['server'].prettyPrint()}")
# Session key
keyblock = cred['key']
print(f"Key type: {keyblock['keytype']}")
print(f"Key value: {hexlify(keyblock['keyvalue'])}")
# Times
times = cred['time']
print(f"Auth time: {datetime.fromtimestamp(times['authtime'])}")
print(f"Start time: {datetime.fromtimestamp(times['starttime'])}")
print(f"End time: {datetime.fromtimestamp(times['endtime'])}")
print(f"Renew till: {datetime.fromtimestamp(times['renew_till'])}")
# Flags
print(f"Flags: 0x{cred['tktflags']:x}")
Retrieving Credentials
Get Credential by SPN
Find cached ticket for service:ccache = CCache.loadFile('/tmp/krb5cc_1000')
# Get TGT
tgt_cred = ccache.getCredential('krbtgt/[email protected]')
if tgt_cred:
print("Found TGT")
tgt = tgt_cred.toTGT()
# Get service ticket
tgs_cred = ccache.getCredential('cifs/[email protected]')
if tgs_cred:
print("Found service ticket")
tgs = tgs_cred.toTGS()
Match Any SPN
Find ticket with flexible matching:# Match without port
cred = ccache.getCredential('cifs/server.domain.local', anySPN=True)
# Will match:
# - cifs/[email protected]
# - cifs/server.domain.local:[email protected]
# - cifs/[email protected]
Iterate All Credentials
for i, cred in enumerate(ccache.credentials):
print(f"[{i}] {cred['server'].prettyPrint()}")
print(f" Expires: {datetime.fromtimestamp(cred['time']['endtime'])}")
Credential Conversion
Convert to TGT Format
Convert cached credential to TGT dict:tgt_cred = ccache.getCredential('krbtgt/[email protected]')
if tgt_cred:
tgt = tgt_cred.toTGT()
# TGT dictionary:
# {
# 'KDC_REP': bytes, # Encoded AS-REP
# 'cipher': cipher_class, # Cipher object
# 'sessionKey': Key_object # Session key
# }
# Use for TGS-REQ
from impacket.krb5.kerberosv5 import getKerberosTGS
tgs, cipher, oldKey, sessionKey = getKerberosTGS(
serverName,
domain,
kdcHost,
tgt['KDC_REP'],
tgt['cipher'],
tgt['sessionKey']
)
Convert to TGS Format
Convert cached credential to TGS dict:tgs_cred = ccache.getCredential('cifs/[email protected]')
if tgs_cred:
tgs = tgs_cred.toTGS()
# TGS dictionary format same as TGT
# Use for service authentication
Change SPN
Modify service principal in ticket:# Get credential
cred = ccache.getCredential('cifs/[email protected]')
# Convert with new SPN
tgs = cred.toTGS(newSPN='http/server.domain.local')
# Note: This modifies the ticket's sname field
# May not work if server validates ticket strictly
Kirbi Format
Load Kirbi File
Load Mimikatz .kirbi file:ccache = CCache.loadKirbiFile('ticket.kirbi')
# Now use as normal ccache
ccache.saveFile('/tmp/krb5cc_converted')
Save as Kirbi
Convert ccache to Mimikatz format:ccache = CCache.loadFile('/tmp/krb5cc_1000')
ccache.saveKirbiFile('ticket.kirbi')
Kirbi Format Details
Kirbi files use KRB-CRED ASN.1 structure:from impacket.krb5.asn1 import KRB_CRED, EncKrbCredPart
from pyasn1.codec.der import decoder, encoder
# Load kirbi
with open('ticket.kirbi', 'rb') as f:
data = f.read()
krbCred = decoder.decode(data, asn1Spec=KRB_CRED())[0]
encKrbCredPart = decoder.decode(
krbCred['enc-part']['cipher'],
asn1Spec=EncKrbCredPart()
)[0]
# Access ticket info
ticket = krbCred['tickets'][0]
krbCredInfo = encKrbCredPart['ticket-info'][0]
print(f"Key: {krbCredInfo['key']}")
print(f"Service: {krbCredInfo['sname']}")
Principal Structure
Principal Class
Represents Kerberos principal in ccache:class Principal:
header['name_type'] # Principal type
header['num_components'] # Component count
realm # Realm name
components[] # Name components
def prettyPrint(self) # Format as string
def fromPrincipal(principal) # From types.Principal
def toPrincipal(self) # To types.Principal
Create Principal
from impacket.krb5.ccache import Principal as CachePrincipal
from impacket.krb5.types import Principal
from impacket.krb5 import constants
# From types.Principal
principal = Principal('[email protected]',
type=constants.PrincipalNameType.NT_PRINCIPAL.value)
cache_principal = CachePrincipal()
cache_principal.fromPrincipal(principal)
# Create manually
cache_principal = CachePrincipal()
cache_principal.header['name_type'] = 1
cache_principal.header['num_components'] = 1
from impacket.krb5.ccache import CountedOctetString
realm = CountedOctetString()
realm['length'] = len('DOMAIN.LOCAL')
realm['data'] = b'DOMAIN.LOCAL'
cache_principal.realm = realm
component = CountedOctetString()
component['length'] = len('user')
component['data'] = b'user'
cache_principal.components.append(component)
print(cache_principal.prettyPrint()) # b'[email protected]'
Key Block Structure
KeyBlock Classes
Version 3 and 4 have different key formats:class KeyBlockV4(Structure):
structure = (
('keytype','!H=0'),
('etype','!H=0'),
('keylen','!H=0'),
('_keyvalue','_-keyvalue','self["keylen"]'),
('keyvalue',':'),
)
class KeyBlockV3(Structure):
structure = (
('keytype','!H=0'),
('etype','!H=0'),
('etype2', '!H=0'), # Duplicate etype
('keylen','!H=0'),
('_keyvalue','_-keyvalue','self["keylen"]'),
('keyvalue',':'),
)
Access Key Data
keyblock = credential['key']
print(f"Key type: {keyblock['keytype']}")
print(f"Encryption: {keyblock['etype']}")
print(f"Length: {keyblock['keylen']}")
print(f"Value: {hexlify(keyblock['keyvalue'])}")
# Create crypto.Key object
from impacket.krb5.crypto import Key
key = Key(keyblock['keytype'], keyblock['keyvalue'])
Time Structure
Times Class
Stores ticket validity times:class Times(Structure):
structure = (
('authtime','!L=0'),
('starttime','!L=0'),
('endtime','!L=0'),
('renew_till','!L=0'),
)
Access Times
from datetime import datetime
times = credential['time']
print(f"Auth: {datetime.fromtimestamp(times['authtime']).isoformat()}")
print(f"Start: {datetime.fromtimestamp(times['starttime']).isoformat()}")
print(f"End: {datetime.fromtimestamp(times['endtime']).isoformat()}")
print(f"Renew: {datetime.fromtimestamp(times['renew_till']).isoformat()}")
# Check if expired
now = datetime.now().timestamp()
if times['endtime'] < now:
print("Ticket expired")
Practical Examples
Dump Ccache Contents
from impacket.krb5.ccache import CCache
from datetime import datetime
from binascii import hexlify
def dump_ccache(filename):
ccache = CCache.loadFile(filename)
if not ccache:
print("Failed to load ccache")
return
print(f"Primary Principal: {ccache.principal.prettyPrint().decode()}")
print(f"\nCredentials ({len(ccache.credentials)}):")
print("=" * 80)
for i, cred in enumerate(ccache.credentials):
print(f"\n[{i}]")
print(f" Client: {cred['client'].prettyPrint().decode()}")
print(f" Server: {cred['server'].prettyPrint().decode()}")
keyblock = cred['key']
print(f" Key Type: {keyblock['keytype']}")
print(f" Key Value: {hexlify(keyblock['keyvalue']).decode()}")
times = cred['time']
print(f" Auth Time: {datetime.fromtimestamp(times['authtime'])}")
print(f" End Time: {datetime.fromtimestamp(times['endtime'])}")
print(f" Flags: 0x{cred['tktflags']:08x}")
print(f" Ticket Size: {cred.ticket['length']} bytes")
dump_ccache('/tmp/krb5cc_1000')
Extract and Use Ticket
import os
from impacket.krb5.ccache import CCache
from impacket.krb5.kerberosv5 import getKerberosTGS
from impacket.krb5.types import Principal
from impacket.krb5 import constants
def use_cached_ticket(target_service):
# Load from environment
ccache_path = os.environ.get('KRB5CCNAME', '/tmp/krb5cc_1000')
ccache = CCache.loadFile(ccache_path)
if not ccache:
print("No ccache found")
return None
# Get TGT
tgt_cred = ccache.getCredential('krbtgt/', anySPN=True)
if not tgt_cred:
print("No TGT in cache")
return None
tgt = tgt_cred.toTGT()
# Get domain from TGT
domain = tgt_cred['server'].realm['data'].decode('utf-8')
# Request new service ticket
serverName = Principal(
target_service,
type=constants.PrincipalNameType.NT_SRV_INST.value
)
tgs, cipher, oldKey, sessionKey = getKerberosTGS(
serverName,
domain,
None, # Let it discover KDC
tgt['KDC_REP'],
tgt['cipher'],
tgt['sessionKey']
)
return tgs, sessionKey
# Use cached TGT to get new service ticket
tgs, sessionKey = use_cached_ticket('cifs/fileserver.domain.local')
Merge Ccache Files
def merge_ccaches(ccache1_path, ccache2_path, output_path):
# Load both caches
ccache1 = CCache.loadFile(ccache1_path)
ccache2 = CCache.loadFile(ccache2_path)
if not ccache1 or not ccache2:
print("Failed to load one or both ccaches")
return
# Use first principal
merged = CCache()
merged.principal = ccache1.principal
merged.headers = ccache1.headers
# Combine credentials
merged.credentials = ccache1.credentials[:]
# Add credentials from second cache if not duplicate
for cred2 in ccache2.credentials:
spn2 = cred2['server'].prettyPrint()
# Check if already exists
duplicate = False
for cred1 in ccache1.credentials:
if cred1['server'].prettyPrint() == spn2:
duplicate = True
break
if not duplicate:
merged.credentials.append(cred2)
# Save merged cache
merged.saveFile(output_path)
print(f"Merged {len(merged.credentials)} credentials")
merge_ccaches('/tmp/krb5cc_1000', '/tmp/krb5cc_backup', '/tmp/krb5cc_merged')
Convert Between Formats
def convert_ccache_kirbi(input_file, output_file, to_kirbi=True):
if to_kirbi:
# Ccache -> Kirbi
ccache = CCache.loadFile(input_file)
if ccache:
ccache.saveKirbiFile(output_file)
print(f"Converted to Kirbi: {output_file}")
else:
# Kirbi -> Ccache
ccache = CCache.loadKirbiFile(input_file)
if ccache:
ccache.saveFile(output_file)
print(f"Converted to ccache: {output_file}")
# Usage
convert_ccache_kirbi('/tmp/krb5cc_1000', 'ticket.kirbi', to_kirbi=True)
convert_ccache_kirbi('ticket.kirbi', '/tmp/krb5cc_new', to_kirbi=False)
Renew Expired Ticket
from impacket.krb5.kerberosv5 import getKerberosTGS
from datetime import datetime
def renew_ticket(ccache_path, spn):
ccache = CCache.loadFile(ccache_path)
# Find credential
cred = ccache.getCredential(spn)
if not cred:
print(f"Credential not found: {spn}")
return
# Check if renewable
times = cred['time']
now = datetime.now().timestamp()
if times['renew_till'] < now:
print("Ticket not renewable (past renew-till time)")
return
# Get TGT for renewal
tgt_cred = ccache.getCredential('krbtgt/', anySPN=True)
if not tgt_cred:
print("No TGT found")
return
tgt = tgt_cred.toTGT()
domain = tgt_cred['server'].realm['data'].decode('utf-8')
# Renew ticket
serverName = Principal(spn, type=constants.PrincipalNameType.NT_SRV_INST.value)
tgs, cipher, oldKey, sessionKey = getKerberosTGS(
serverName,
domain,
None,
tgt['KDC_REP'],
tgt['cipher'],
tgt['sessionKey'],
renew=True
)
# Update cache
ccache.fromTGS(tgs, oldKey, sessionKey)
ccache.saveFile(ccache_path)
print(f"Renewed ticket for {spn}")
Security Considerations
File Permissions
Protect ccache files:import os
ccache_path = '/tmp/krb5cc_1000'
# Set restrictive permissions (0600)
os.chmod(ccache_path, 0o600)
# Verify ownership
stat_info = os.stat(ccache_path)
if stat_info.st_uid != os.getuid():
print("Warning: Ccache owned by different user")
Credential Expiration
Check ticket validity:from datetime import datetime
def check_expiration(ccache_path):
ccache = CCache.loadFile(ccache_path)
now = datetime.now().timestamp()
for cred in ccache.credentials:
server = cred['server'].prettyPrint().decode()
endtime = cred['time']['endtime']
if endtime < now:
print(f"EXPIRED: {server}")
else:
remaining = datetime.fromtimestamp(endtime) - datetime.now()
print(f"Valid for {remaining}: {server}")