SAML SSO Exploitation: Breaking the Trust Chain
9 min read
June 14, 2026

Table of contents
👋 Introduction
Hey everyone!
Last week we covered Entra ID SyncJacking, where an on-prem AD Connect agent becomes the pivot point for full cloud admin takeover. This week we move one layer up: the SSO protocol that hands out the keys to everything sitting behind that identity.
SAML is the authentication backbone of enterprise SaaS. One bypass clears the gate to every app in scope. The signing model sounds solid until you understand that signature validation and assertion processing are two separate code paths that often read different parts of the same XML document. Decades of XML parser quirks mean those two paths regularly disagree, and attackers land in the gap.
This week: XSW attacks, void canonicalization, comment injection into NameID, attribute-based escalation, replay, and the tooling that automates all of it.
Let’s get into it 👇
🔑 How SAML Works: The Parts That Get You Exploited
In an SP-initiated flow, the Service Provider (SP) redirects the browser to the Identity Provider (IdP) with a SAMLRequest parameter. The user authenticates at the IdP, which issues a SAMLResponse containing a signed XML Assertion. The browser POSTs that response to the SP’s Assertion Consumer Service (ACS) URL.
The Assertion contains the NameID (the identity claim, usually an email address), attributes like roles and groups, and validity constraints including NotBefore, NotOnOrAfter, and Conditions with an AudienceRestriction. The IdP signs either the Assertion, the Response, or both. The SP trusts whatever the IdP signed.
<samlp:Response>
<Issuer>https://idp.example.com</Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<saml:Assertion ID="_abc123">
<saml:Issuer>https://idp.example.com</saml:Issuer>
<saml:Subject>
<saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
[email protected]
</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role"><saml:AttributeValue>user</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
<ds:Signature>...</ds:Signature>
</saml:Assertion>
</samlp:Response>
The critical detail: the ds:Reference URI inside the Signature points to the signed element by its ID attribute. Signature validation asks “does this signed element hash correctly?” It does not ask “is this the element my application code will read next?”
🔀 XML Signature Wrapping: The Attack That Won’t Die
XSW exploits the gap between what the XML signature validator validates and what the application parser processes. You move the signed Assertion somewhere the validator still finds it, then insert a forged Assertion in the position the application reads.
SAMLRaider ships eight XSW variants. Here is the structure behind XSW #1, targeting the Response element:
<!-- Attacker's forged Response wraps the legitimate one -->
<samlp:Response ID="_evil">
<!-- Attacker controls all content here -->
<saml:Assertion ID="_evil_assertion">
<saml:NameID>[email protected]</saml:NameID>
<saml:AttributeStatement>
<saml:Attribute Name="role"><saml:AttributeValue>admin</saml:AttributeValue></saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
<!-- Original signed assertion buried here, signature still valid -->
<samlp:Extensions>
<saml:Assertion ID="_abc123">
... original signed content ...
<ds:Signature>...</ds:Signature>
</saml:Assertion>
</samlp:Extensions>
</samlp:Response>
The validator resolves Reference URI="#_abc123" and finds the original assertion, verifies the hash, and reports success. The application parser uses getElementsByTagName("Assertion")[0] or walks from the root of the document, reads _evil_assertion, and sees [email protected]. Two code paths, two different elements.
XSW #3 through #6 wrap at the Assertion level rather than the Response level. XSW #7 and #8 use the Extensions element as the container for the forged content. The right variant depends on how the SP implementation walks the XML tree. In practice, you try all eight in SAMLRaider until one produces a session as a different user.
💬 Void Canonicalization and Comment Injection
Canonicalization defines a canonical byte representation of an XML element before hashing it for the signature digest. PortSwigger researcher Zak Fedotkin’s Fragile Lock research documented two attacks against this step that hit Ruby-SAML and PHP-SAML in late 2025 and early 2026.
Void canonicalization triggers when the canonicalization algorithm encounters an unresolved relative URI in a Transform element. Instead of failing hard, most implementations return an empty string. An attacker can craft a signature where the DigestValue is the hash of an empty string, then manipulate the document so canonicalization silently voids out. The SP sees a valid signature over nothing and accepts the forged assertion.
Comment injection is simpler and keeps appearing. Insert an XML comment inside the NameID value:
<saml:NameID>admin<!--injected-->@corp.com</saml:NameID>
If the SP strips comments before verifying the signature but extracts the NameID after stripping, the extracted value becomes [email protected]. If the SP canonicalizes with comments included (Canonical XML 1.0 with comments), the signature validates over the full string including the comment. The authentik platform shipped this exact bug in early 2026, and GitHub’s ruby-saml research documented the parser differential mechanism behind it in detail.
The root issue in both cases: the code path that validates the signature and the code path that extracts the NameID apply different transformations to the same document.
🎭 Attribute Injection and NameID Manipulation
SAML assertions carry attributes beyond identity. Role assignments, group memberships, department codes, custom entitlement flags. Many SPs implement authorization by reading these attributes directly from the assertion and granting access accordingly.
If the SP does not enforce that the full Assertion is signed (only the Response is signed), you can modify individual attributes without breaking the signature. If signature validation is optional or absent on the SP side, you can change anything. The test is direct: intercept a valid SAMLResponse, decode the base64, modify the role attribute value from user to admin, re-encode, and replay.
POST /acs HTTP/1.1
Host: app.target.com
Content-Type: application/x-www-form-urlencoded
SAMLResponse=<base64 of modified XML>
NameID manipulation follows the same pattern when NameID determines user lookup. A SP that fetches the user record with SELECT * FROM users WHERE email = :nameid and trusts the NameID directly is vulnerable to SQL injection, LDAP injection, or identity collision depending on backend. More commonly, changing the NameID to another user’s email impersonates that user entirely if the SP performs no secondary validation.
Also test for signature exclusion. Some SPs treat a missing signature as valid. Remove all <ds:Signature> elements from the Response and Assertion, send an unsigned assertion with any NameID and any attributes. A surprising number of older SP implementations accept it. This is one of the easiest wins in a SAML engagement.
The token recipient confusion attack is worth running on every SP-sharing-IdP engagement: intercept a SAMLResponse issued for SP-A and submit it to SP-B’s ACS endpoint. If both SPs share an IdP but neither validates AudienceRestriction, SP-B accepts a response it was never the intended audience for. Lateral movement across enterprise apps from a single valid session.
🛠️ Tools and Where to Find SAML in the Wild
SAML appears in any app with “Sign in with [corporate IdP]” or “SSO” buttons. The SAMLRequest and SAMLResponse parameters carry base64-encoded (usually deflated) XML. In Burp, they show up in POST bodies to /acs, /saml/acs, or similar ACS paths, and as query string parameters on redirect bindings.
SAMLRaider (Burp extension, v2.5.2) automates most of the above. Install it from the BApp Store, intercept a SAMLResponse, open the SAMLRaider tab in Repeater, and run the eight XSW variants from the dropdown. It also handles signature removal, certificate cloning, and XXE payload injection. Configure a proxy filter for SAMLResponse to focus interception. The PortSwigger BApp listing and source at GitHub/CompassSecurity both confirm version 2.5.2 is current.
maSSO (Doyensec, 2026) is a weaponized compliant IdP for testing SP implementations. Run it locally with Docker Compose, point the SP’s SSO configuration at it, then intercept and modify tokens before signing. It supports SAML 2.0 and OIDC and ships with modes for pass-through, mock, and intercept-before-sign. Useful for testing SP attribute processing when you control the IdP. Available at github.com/doyensec/maSSO.
For replay protection testing, capture a valid SAMLResponse, wait for its NotOnOrAfter validity window to close, and resubmit. If the SP accepts it, there is no time constraint enforcement. A SP that caches consumed assertion IDs prevents replay within the validity window; one that does not cache them accepts the same assertion indefinitely while it stays valid.
Recon step: check WS-Federation metadata endpoints. https://app.target.com/FederationMetadata/2007-06/FederationMetadata.xml often returns the IdP’s Signature element publicly, which the Fragile Lock research shows you can extract and reuse as the legitimate signing material for crafting forged assertions. Microsoft Azure and ADFS expose these by default.
For JWT comparison, the signing model mirrors what we covered in Issue 6 on JWT exploitation: a signature that validates does not mean the payload content is trusted. OAuth SSO email validation attacks from Issue 16 show the same email-as-identity assumption being abused in a different protocol.
🎯 Key Takeaways
SAML’s hardest problem is not cryptography. RSA-SHA256 is fine. The problem is that XML processing requires parsing, canonicalizing, and validating the same document through multiple sequential steps, and each step can make different choices about which element it operates on. Every major XSW variant, every void canonicalization bypass, and every parser differential exploit lives in the space between those steps.
The parser differential pattern keeps recurring because SAML libraries often use two XML parsers internally: one for signature validation and one for data extraction. Ruby-SAML used REXML and Nokogiri. They disagree on attribute precedence when duplicates exist, on whether xmlns is a reserved namespace, and on how DOCTYPE round-trips change comment handling. Each disagreement is a potential bypass. Audit any SAML library you deploy by checking which parsers it uses and whether both paths process the same document identically.
On a pentest engagement with SAML SSO, start with five quick checks: can you modify attributes without invalidating the signature (test role/group values), does removing the signature produce a valid session, do all eight XSW variants fail or does one succeed, does the SP enforce AudienceRestriction and Destination, and can you replay an assertion within its validity window. These five checks alone cover the majority of real-world SAML vulnerabilities and take under 30 minutes with SAMLRaider.
SAML bugs keep appearing not because implementations skip security, but because the XML specification itself has enough flexibility to create these gaps. The WorkOS vulnerability summary for 2026 lists five critical SAML vulnerabilities across Citrix, Cisco, authentik, and OneUptime in the span of four months. This attack surface is active.
Practice:
- PortSwigger: The Fragile Lock - void canonicalization and parser differential SAML bypasses, published December 2025
- GitHub Blog: Sign in as anyone - ruby-saml parser differential walkthrough with full exploitation detail
- SAMLRaider on PortSwigger BApp Store - install directly from Burp
- SAMLRaider source (CompassSecurity) - all eight XSW variants documented
- maSSO by Doyensec - weaponized IdP for SP testing
- VulnerableSAMLApp - Docker-based vulnerable SP/IdP pair for local practice
- PentesterLab SAML exercise - signature stripping hands-on (PRO)
- OWASP SAML Security Cheat Sheet - canonical mitigation reference
Thanks for reading, and happy hunting!
— Ruben
Other Issues
Previous Issue
💬 Comments Available
Drop your thoughts in the comments below! Found a bug or have feedback? Let me know.