Lesson 1 of 3 · 25 min read

Kerberos Attack Detection — Golden Tickets, Silver Tickets, and Kerberoasting

Kerberos attacks are forensically challenging because they abuse legitimate protocol features — the attacker is not exploiting a vulnerability, they are using stolen cryptographic material correctly.


Golden Ticket Detection

# Search for Golden Ticket indicators in Windows Security Event Log

# Event 4769 with RC4 encryption (anomalous in AES domain)
Get-WinEvent -FilterHashtable @{
  LogName = 'Security'
  Id = 4769
} | ForEach-Object {
  $xml = [xml]$_.ToXml()
  $encType = $xml.Event.EventData.Data | 
    Where-Object {$_.Name -eq 'TicketEncryptionType'} | 
    Select-Object -ExpandProperty '#text'
  if ($encType -eq '0x17') {
    [PSCustomObject]@{
      Time = $_.TimeCreated
      Account = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'AccountName'}).'#text'
      Service = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'ServiceName'}).'#text'
      ClientIP = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'IpAddress'}).'#text'
      EncType = $encType
    }
  }
} | Sort-Object Time

# Event 4624 with no preceding 4768 within time window (ticket used without TGT issuance)
# Requires correlation — use SIEM rather than raw event log
# Splunk query example:
# index=wineventlog EventCode=4624 Logon_Type=3
# | join type=left Account_Name [search index=wineventlog EventCode=4768 | rename Account_Name as Account_Name]
# | where isnull(EventCode_from_4768)

# Check for impossible ticket lifetimes (Golden Tickets often set to 10 years = 87600 hours)
# This requires checking Kerberos ticket cache on endpoint, not DC event logs
# On endpoint: klist
# Look for tickets with end times years in the future
klist

Kerberoasting Detection

# Find all SPNs in the domain (what the attacker would enumerate first)
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} `
  -Properties ServicePrincipalName, PasswordLastSet, LastLogonDate, MemberOf |
  Select-Object SamAccountName, ServicePrincipalName, PasswordLastSet, LastLogonDate |
  Format-Table -AutoSize

# Find service accounts with weak (crackable) passwords
# These are the highest-risk Kerberoasting targets
Get-ADUser -Filter {ServicePrincipalName -ne "$null"} `
  -Properties PasswordLastSet, PasswordNeverExpires |
  Where-Object {$_.PasswordLastSet -lt (Get-Date).AddYears(-1) -or $_.PasswordNeverExpires -eq $true} |
  Select-Object SamAccountName, PasswordLastSet, PasswordNeverExpires

# Detect Kerberoasting activity in Event 4769
# Look for RC4 TGS requests from non-service accounts
$kerberoast_events = Get-WinEvent -FilterHashtable @{
  LogName = 'Security'
  Id = 4769
  StartTime = (Get-Date).AddDays(-1)
} | Where-Object {
  $xml = [xml]$_.ToXml()
  $encType = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TicketEncryptionType'}).'#text'
  $encType -eq '0x17'
}

# Group by requesting account to identify burst activity
$kerberoast_events | ForEach-Object {
  $xml = [xml]$_.ToXml()
  ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'AccountName'}).'#text'
} | Group-Object | Sort-Object Count -Descending

# Find accounts with preauth disabled (AS-REP Roasting targets)
Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} `
  -Properties DoesNotRequirePreAuth, PasswordLastSet |
  Select-Object SamAccountName, PasswordLastSet

Mimikatz Detection in Event Logs

Mimikatz leaves specific traces when extracting credentials or manipulating Kerberos tickets:

# Event 4611 — Trusted logon process registered (Mimikatz sekurlsa requires this)
# Event 4614 — Notification package loaded (Mimikatz wdigest enable)
# Event 10 (Sysmon) — Process access to LSASS with specific access rights

# Sysmon Event 10: LSASS access (credential dumping)
Get-WinEvent -FilterHashtable @{
  LogName = 'Microsoft-Windows-Sysmon/Operational'
  Id = 10
} | Where-Object {
  $xml = [xml]$_.ToXml()
  $target = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'TargetImage'}).'#text'
  $target -like '*lsass*'
} | ForEach-Object {
  $xml = [xml]$_.ToXml()
  [PSCustomObject]@{
    Time = $_.TimeCreated
    Source = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'SourceImage'}).'#text'
    GrantedAccess = ($xml.Event.EventData.Data | Where-Object {$_.Name -eq 'GrantedAccess'}).'#text'
  }
} | Where-Object {$_.GrantedAccess -in @('0x1010', '0x1038', '0x143a', '0x1fffff')}
# 0x1010 = PROCESS_VM_READ + PROCESS_QUERY_INFORMATION (Mimikatz minimum)
# 0x1fffff = PROCESS_ALL_ACCESS (most aggressive — clearly malicious)