In September 2024, CVE-2024-45409 received a CVSS score of 10.0. Anyone with a single valid SAML document from a GitLab organization could log in as any user on any self-managed instance, including site administrator. No stolen private key. No brute force.
The SAML signature authenticates a specific XML element, not the entire document. XSW attacks leave that element intact and insert a forged one where only the application reads. Account takeover in enterprise SSO is a structural consequence of how SAML parsers disagree with themselves.
SAML validation is not what you think it validates
The SAML 2.0 Core spec defines <ds:Signature> with a <ds:Reference URI='#id123'> pointing to a specific XML element by its ID attribute. The Service Provider verifies the signature over that identified element. Then it queries the document via XPath to read NameID and user attributes.
These are two independent operations. Nothing binds both to the same node. The validator passes; the application reads attacker data.
The XPath //saml:Assertion returns the first matching element in document order, not the element referenced by the signature. When an attacker inserts a forged element before the signed one, that XPath returns the forged element. The signature validator never examines the forged element because it only verifies the URI-referenced one.
The USENIX Security 2012 paper tested 14 SAML implementations. All were vulnerable to at least one XSW variant. None enforced that the verified element and the application-read element were identical.
Eight ways to make the validator and the application read different nodes
The USENIX Security 2012 paper (Somorovsky et al.) cataloged eight structurally distinct positions. In each variant, the validator finds the legitimate signed node while the SP's XPath returns the attacker's node.
XSW1: the forged assertion replaces the original in the SAML Response; the original is moved to an <Extensions> element. XSW2: the forged assertion is inserted before the signed one in document order. XSW3: the original signed assertion is nested inside the forged one as a child; the SP reads the outer, unsigned element. XSW4: the inverse, with the forged assertion as a child of the signed one in document structure.
XSW5: the <Signature> element is moved into the forged assertion's subtree; the original is hidden in <Extensions>. XSW6: a variation of XSW5 with different canonicalization rules. XSW7: a duplicate ID attribute on the forged element, with parser behavior determining which element wins the getElementById lookup. XSW8: a <samlp:Extensions> wrapper hides the original signed assertion while the forged one appears at the response root level.
All eight variants pass signature validation because the original signed element remains structurally intact. The signature verifies correctly. The SP authenticates the attacker.
Zero key material required
The attacker needs only one legitimately signed SAML response from the target organization. Any valid account generates that document.
The process has six steps. First, the attacker logs in normally and captures the base64-encoded SAMLResponse in the POST binding. Second, decodes the XML and identifies the signed Assertion element and its ID attribute. Third, clones the element and modifies the NameID to the victim's identifier. Fourth, positions the forged clone using one of the XSW1-8 variants, leaving the original signed element in place. Fifth, re-encodes and replays to the SP. Sixth, the SP's //saml:Assertion[1] XPath returns the forged clone; the application creates a session for the victim.
In CVE-2024-45409, the attacker injects a forged DigestValue inside samlp:Extensions. The document-scoped XPath //ds:DigestValue retrieves the attacker's value before the legitimate one in SignedInfo. The signature over the original element remains valid. The session the SP creates belongs to the target.
Seventeen years of the same structural flaw
USENIX Security 2012 cataloged XSW as a theoretical class. Five years later, the same flaw appeared in production across every major library simultaneously.
In 2017, CERT published VU#475445. The batch covered CVE-2017-11427 (python-saml), CVE-2017-11428 (ruby-saml 1.6.0), CVE-2017-11429 (saml2-js), CVE-2017-11430 (OmniAuth), and CVE-2018-0489 (Shibboleth OpenSAML-C). All shared the same root: document-scoped XPath over a document the attacker partially controls.
In September 2024, CVE-2024-45409 hit CVSS 10.0 in ruby-saml. GitLab CE/EE versions 16.1 through 17.3.x were exposed to anyone with a valid SAML document from the organization. Synacktiv published a working exploit in October 2024. The patch shipped in versions 17.3.3, 17.2.7, 17.1.8, 17.0.8, and 16.11.10.
In March 2025, GitHub Security Lab published CVE-2025-25291 and CVE-2025-25292. The ruby-saml library used REXML and Nokogiri on the same document; both parsers returned different elements for identical XPath queries. Nokogiri canonicalized the assertion for the hash; REXML extracted the expected DigestValue. No cryptographic binding connected the two operations. HackerOne report #2579939 showed this gave site administrator access on GitHub Enterprise Server instances.
SAML SSO in APIs multiplies the blast radius
When SAML backs API gateway authentication or AWS IAM federation, a successful XSW attack does not create a browser session. It mints AWS temporary credentials scoped to the victim's IAM role.
In AssumeRoleWithSAML, assertion attributes determine which IAM role is assumed. An attacker who forges the role attribute to AdministratorAccess receives temporary credentials with administrator permissions over the AWS account. HackerOne report #356284 demonstrated signature wrapping in the samlify library (Node.js), exposing the npm ecosystem.
API gateways with SAML authorizers pass the decoded assertion to upstream services. Upstream code reading attributes from the unverified element is equally vulnerable. One forged assertion reaches every service that trusts the same IdP.
Fixing the split
The canonical fix is not switching the signature algorithm. It is ensuring the application reads attributes from the same node the validator verified.
Five concrete actions, in priority order. Mandatory ID-binding: after verifying the signature over Reference URI='#id', retrieve the element by that ID via getElementById, not via //saml:Assertion. Read attributes exclusively from that node. Scoped XPath: replace //ds:DigestValue with SignedInfo/ds:DigestValue relative to the verified context node. ruby-saml 1.12.3 and 1.16.0 shipped this fix for CVE-2024-45409.
Strict schema validation before signature processing: reject documents with multiple Assertion elements, ID collisions, or Extensions elements containing children in the ds: namespace. Library pinning: ruby-saml >= 1.16.0, python3-saml >= 1.16.0, samlify >= 2.8.5. A SAML library without a pinned version in production is an unpatched attack surface. SIEM detection: alert on SAMLResponse documents with more than one Assertion element or Extensions elements containing ds: namespace children.
The MAGO Intel tool (intel.mago.team) probes SAML endpoints and checks library versions against known-vulnerable ranges. This runs as part of technology detection and authentication audit routines in API security assessments.
SAML has been in production for over two decades. The XSW vulnerability class has existed for most of that time. The fix requires one architectural change: ensure that attribute extraction and signature validation operate on the same node reference. "Signature verified" does not mean "document authenticated." It means one specific element was authenticated. The application must prove that the element it reads for authorization is that same element.
Top comments (0)