Chapter 65

Credential Theft Traffic

Credential theft is a network-visible event when the attacker's tool requests authentication material from the domain controller or captures challenges from network traffic. Kerberoasting, AS-REP Roasting, NTLM relay, and credential harvesting via phishing all leave distinctive network fingerprints. This chapter covers the detection of each from captured traffic.

Scenario

During a red team exercise, the red team uses Rubeus to Kerberoast 12 service accounts in 8 seconds. The blue team's Zeek-based detection fires 4 seconds in — before the red team finishes. The detection: more than 5 TGS-REQ to the domain controller from the same source within 10 seconds. The captured traffic reveals exactly which service accounts were targeted and the encrypted ticket blobs that could be used for offline cracking.

Kerberos Attack Traffic

  Kerberoasting Network Fingerprint
  ═══════════════════════════════════════════════════════════════════

  Normal TGS request pattern:
  User logs in → AS-REQ → AS-REP (TGT)
                → TGS-REQ for specific service they're accessing
                (1-3 TGS requests per hour, for services the user actually uses)

  Kerberoasting pattern:
  Attacker → TGS-REQ for SPN1 → TGS-REQ for SPN2 → TGS-REQ for SPN3
              → TGS-REQ for SPN4 → TGS-REQ for SPN5 ...
  (Many TGS requests in rapid succession, for services the user never accesses)
  (All encrypted with RC4 if attacker specified RC4 — weaker, faster to crack)

  PCAP indicators:
  ├── Multiple TGS-REQ from same src to DC port 88 within seconds
  ├── Each TGS-REQ has different sname (SPN being requested)
  ├── Etype in TGS-REQ = 23 (RC4-HMAC) → offline cracking optimized
  └── TGS-REP contains encrypted service ticket (the hash to crack)

  AS-REP Roasting fingerprint:
  ├── AS-REQ with no preauthentication (PA-DATA field absent or type PA-NONE)
  ├── Immediately followed by AS-REP with encrypted_part (hash to crack)
  └── Affects accounts with "Do not require Kerberos preauthentication" set

Detection Queries

bashcredential-theft-detection.sh
#!/bin/bash
PCAP="${1:-capture.pcap}"

echo "=== Kerberoasting: bulk TGS-REQ to DC ==="
tshark -r "$PCAP" \
    -Y "kerberos.msg_type==12" \
    -T fields -e frame.time -e ip.src -e ip.dst \
    -e kerberos.req.sname \
    2>/dev/null | \
    awk -F'\t' '{
        src[$2]++
        spns[$2] = spns[$2] " " $4
    }
    END {
        for (s in src)
            if (src[s] > 5)
                printf "KERBEROASTING: %d TGS-REQ from %s\nSPNs: %s\n\n", src[s], s, spns[s]
    }'

echo ""
echo "=== AS-REP Roasting: AS-REQ without preauthentication ==="
# Kerberos msg_type 10 = AS-REQ
# Check for absence of preauth data
tshark -r "$PCAP" \
    -Y "kerberos.msg_type==10" \
    -T fields -e frame.time -e ip.src -e ip.dst \
    -e kerberos.req.cname \
    2>/dev/null | head -20

echo ""
echo "=== NTLM relay indicators: source sends NTLM auth to unexpected destinations ==="
# NTLM challenge-response from internal to internal (relay scenario)
tshark -r "$PCAP" \
    -Y "ntlmssp.messagetype==3" \
    -T fields -e frame.time -e ip.src -e ip.dst \
    -e ntlmssp.auth.username \
    -e ntlmssp.auth.domain \
    2>/dev/null | head -20

echo ""
echo "=== NTLM capture: NTLMv2 hash extraction (for reference) ==="
# Uses tshark to extract fields for hashcat format: user::domain:challenge:response
python3 -c "
import subprocess
import sys

result = subprocess.run([
    'tshark', '-r', '$PCAP',
    '-Y', 'ntlmssp.messagetype==3',
    '-T', 'fields',
    '-e', 'ntlmssp.auth.username',
    '-e', 'ntlmssp.auth.domain',
    '-e', 'ntlmssp.auth.ntresponse',
], capture_output=True, text=True)

for line in result.stdout.splitlines():
    parts = line.split('\t')
    if len(parts) >= 3:
        user, domain, resp = parts[0], parts[1], parts[2]
        if resp and len(resp) > 48:  # NTLMv2 response is 24+ bytes
            challenge = '0000000000000000'  # Need from type 2 message
            print(f'{user}::{domain}:{challenge}:{resp[:32]}:{resp[32:]}')
"

echo ""
echo "=== Zeek kerberos.log: Kerberoasting query ==="
awk 'NR>8 && !/^#/' /opt/zeek/logs/current/kerberos.log 2>/dev/null | \
    awk -F'\t' '$7 == "TGS" {print $3}' | \
    sort | uniq -c | sort -rn | \
    awk '$1 > 5 {print "KERBEROAST_CANDIDATE:", $1, "TGS requests from", $2}'
Why RC4 in TGS-REQ is a Kerberoasting indicator

Kerberoasting depends on the attacker requesting a service ticket encrypted with a weak algorithm that can be cracked offline. Modern Kerberos implementations default to AES256 for service tickets. Kerberoasting tools (Rubeus, Impacket's GetUserSPNs.py) explicitly request RC4-HMAC (etype 23) encryption for the returned ticket because RC4-HMAC is faster to crack than AES256. In the PCAP, the etype field in the TGS-REQ request body shows what the attacker asked for. Legitimate clients almost never request etype 23 explicitly — they use what the KDC negotiates, which is AES256 in modern environments. Detection: a TGS-REQ with etype 23 in 2024+ is nearly always Kerberoasting or a very old client. The Zeek kerberos.log logs the cipher field — filter for cipher=="rc4-hmac" in bulk TGS requests. Note that some environments have legacy service accounts that still require RC4 (SQL Server service accounts are common); these need to be allowlisted by SPN.

Q & A

Q: I'm seeing Kerberoasting alerts that all come from our vulnerability scanner running ADExplorer. How do I differentiate legitimate auditing from real attacks?

This is a tuning problem, not a Kerberoasting detection gap. Solutions: (1) Allowlist by source IP: add the vulnerability scanner IP to your detection's exclusion list. But document that the scanner is excluded — if it's ever compromised, you've blinded yourself to attacks from it. (2) Allowlist by account: if the scanner uses a dedicated service account, filter out TGS-REQ from that account's principal name. Less brittle than IP allowlisting if the scanner moves. (3) Alert only on RC4 etype requests: your scanner is likely using the default (AES256). Real Kerberoasting uses RC4. Filter your detection to only fire on bulk TGS-REQ with etype 23. (4) Time-based alerting: scanners run on a schedule. Real Kerberoasting is ad-hoc. If you see bulk TGS-REQ exactly at scan time, it's probably the scanner; bulk TGS-REQ at 2 AM from an analyst workstation is not. (5) Rate vs. scope: your scanner targets all SPNs systematically. An attacker may be more targeted. The scanner will hit hundreds of SPNs; the attacker may only hit a handful — but etype 23 from an unexpected source at an unexpected time.