Researched: GitHub Advisory Database, NVD, CERT/CC, Mattermost Security Blog, project-specific advisories.
XSW attacks exploit the disconnect between signature verification and data extraction in SAML processors. The attacker moves or copies the signed content to a different location in the XML document, then inserts malicious content where the SP will read it. The signature remains valid over the original content, but the SP processes the attacker's injected content.
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2024-45409 | ruby-saml | Critical (10.0) | 2024 |
| CVE-2025-47949 | samlify (npm) | Critical | 2025 |
| CVE-2025-46572 | passport-wsfed-saml2 (npm) | Critical | 2025 |
| CVE-2025-54419 | @node-saml/node-saml (npm) | Critical | 2025 |
| CVE-2025-54369 | @node-saml/node-saml (npm) | Critical | 2025 |
| CVE-2016-5697 | ruby-saml | High | 2016 |
| CVE-2016-1000253 | onelogin/php-saml | Moderate | 2016 |
| CVE-2020-5407 | Spring Security SAML | High | 2020 |
| CVE-2024-6800 | GitHub Enterprise Server | Critical | 2024 |
| CVE-2024-6202 | HaloITSM | Critical | 2024 |
| CVE-2023-34923 | TOPdesk | High | 2023 |
| CVE-2020-13415 | Aviatrix Controller | Moderate | 2020 |
XSW attacks come in multiple variants (XSW1-XSW8), each placing the malicious content in a different part of the XML document tree:
- XSW1: Attacker adds a new unsigned Assertion before the signed one, keeping the signature intact
- XSW2: Wraps a new unsigned
Responsearound the signed content - XSW3-8: Various combinations of moving the signed
AssertionintoExtensions,Object, or other elements while the SP reads an unsigned copy
Ruby-SAML <= 12.2 and 1.13.0 <= 1.16.0 did not properly verify the signature of the SAML Response. An unauthenticated attacker with access to any signed SAML document could forge a SAML Response/Assertion with arbitrary contents, logging in as any user.
- Ensure the signature references the correct element: The signature's
Reference URImust point to the exact element being processed - Validate that signed content is what's being read: After signature verification, ensure the data extracted for authentication decisions comes from the verified (signed) subtree
- Reject documents with multiple Assertions (unless all are individually verified)
- Use strict XPath for extracting signed elements: Don't just search for the first
Assertionelement; verify it's the one referenced by the signature
HIGH. Go SAML libraries have been specifically vulnerable (see gosaml2 CVE-2020-29509). The implementation must:
- Verify the signature covers the exact Assertion being processed
- Reject responses with multiple Assertions unless individually signed
- Validate Reference URI matches the Assertion's ID attribute
- Not trust any assertion data that wasn't within the signed subtree
This is the most critical class of vulnerabilities specific to Go SAML implementations. Go's encoding/xml package does not preserve XML semantics during tokenization round-trips. Maliciously crafted XML mutates when parsed and re-serialized, meaning the signature verification stage sees different content than the data extraction stage.
| CVE | Component | Severity | Year |
|---|---|---|---|
| CVE-2020-29509 | Go encoding/xml (attributes) | Critical (9.8) | 2020 |
| CVE-2020-29510 | Go encoding/xml (directives) | Critical (9.8) | 2020 |
| CVE-2020-29511 | Go encoding/xml (elements) | Critical (9.8) | 2020 |
| CVE-2020-27846 | crewjam/saml | Critical (9.8) | 2020 |
| CVE-2020-26290 | dex (SAML connector) | Critical | 2020 |
| CVE-2020-26276 | fleetdm/fleet | High | 2020 |
- github.com/russellhaering/gosaml2 - Fixed in v0.6.0
- github.com/crewjam/saml - Fixed in v0.4.3
- github.com/dexidp/dex - Fixed in v2.27.0
- github.com/fleetdm/fleet - Fixed
- 20+ commercial products contacted during coordinated disclosure by Mattermost
Three independently exploitable vulnerabilities in Go's encoding/xml:
-
Attribute instability (CVE-2020-29509): Namespace prefixes on XML attributes are not correctly preserved during tokenization round-trips. The observed namespace and local name of attributes can change between parse cycles.
-
Directive instability (CVE-2020-29510): XML directives (DTD, processing instructions) mutate during round-trips.
-
Element instability (CVE-2020-29511): Element names/namespaces can change during round-trips.
Impact on SAML: An attacker can craft a signed SAML response that appears correctly signed during verification, but when the SP re-parses the XML to extract claims, the mutation causes different elements to be read. This enables:
- Full authentication bypass: Attacker impersonates any user
- Arbitrary privilege escalation: Within the scope of the SAML SP
- Use github.com/mattermost/xml-roundtrip-validator to validate XML documents before processing
- Avoid re-serializing/re-parsing XML between signature verification and data extraction
- The Go team has NOT fully fixed the underlying
encoding/xmlissues (as of the disclosure) - Go 1.16+ added API to disable namespace prefix parsing but this doesn't fully resolve the issue
CRITICAL. This is THE fundamental vulnerability class for Go SAML implementations. Any Go SAML SP that uses encoding/xml for both signature verification and data extraction is potentially vulnerable. The gosaml2 library specifically had to integrate the Mattermost xml-roundtrip-validator as a mitigation.
The famous 2018 vulnerability (Duo Labs / CERT VU#475445) discovered that multiple SAML libraries incorrectly handle XML comments within text nodes during canonicalization. When an XML comment is placed inside a text node (e.g., user@evil.com<!-- -->admin), some libraries strip the comment and concatenate the text, while others only read text before or after the comment.
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2017-11427 | OneLogin python-saml | High | 2018 |
| CVE-2017-11428 | OneLogin ruby-saml | High | 2018 |
| CVE-2017-11429 | Clever saml2-js | Moderate | 2018 |
| CVE-2017-11430 | OmniAuth-SAML | High | 2018 |
| CVE-2018-0489 | Shibboleth openSAML C++ | High | 2018 |
| CVE-2018-5387 | Wizkunde SAMLBase | High | 2018 |
| CVE-2018-7340 | Duo Network Gateway | High | 2018 |
| CVE-2025-66568 | ruby-saml (Libxml2 C14N) | Critical | 2025 |
| CVE-2025-66567 | ruby-saml (namespace handling) | Critical | 2025 |
Discovered by: Kelby Ludwig of Duo Security (Duo Labs)
Affected vendors confirmed: Clever Inc., Duo Security, OmniAuth, OneLogin Inc., Pulse Secure, Shibboleth Consortium, Wizkunde B.V. Not affected: AssureBridge, Box, CA Technologies (+ 16 more vendors evaluated)
- XML DOM traversal APIs inconsistently handle comments within text nodes
- Some APIs return only pre-comment text when getting
innerText - Signature verification uses canonicalization (which preserves all text), but the application reads truncated text
- Example: NameID
admin@evil.com<!--INJECTED-->.legit.commight be signed as-is, but the SP reads onlyadmin@evil.com
CVE-2025-66568 and CVE-2025-66567 show this class of vulnerability continues to recur:
- CVE-2025-66568: ruby-saml vulnerable via Libxml2 canonicalization error that bypasses Digest/Signature validation
- CVE-2025-66567: ruby-saml SAML authentication bypass due to namespace handling (parser differential)
- Never use different parsers/APIs for verification vs. data extraction
- Reject XML with comments in security-critical text nodes
- Use the same canonicalized form for both signature check and value extraction
- Validate that text node content matches expected patterns (no embedded comments)
HIGH. Go's encoding/xml strips comments during parsing, so the signed canonicalized form (which may include comments) could differ from what Go reads. This overlaps with Attack Class 2. Mitigation: ensure the XML is validated with the roundtrip validator before processing.
An attacker injects additional unsigned Assertion elements into a signed SAML Response. If the SP doesn't properly verify which Assertion was signed, it may process the attacker's unsigned Assertion.
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2022-41912 | crewjam/saml (Go) | Critical (9.8) | 2022 |
| CVE-2025-54369 | @node-saml/node-saml | Critical | 2025 |
| CVE-2021-23365 | tyk-identity-broker (Go) | Critical | 2021 |
The crewjam/saml Go library prior to version 0.4.9 was vulnerable to authentication bypass when processing SAML responses containing multiple Assertion elements. The library verified the signature of one Assertion but read claims from a different one.
- Reject SAML responses containing multiple Assertion elements (unless each is individually verified)
- Ensure the Assertion being processed is the same one whose signature was verified
- Use the Assertion's ID attribute to correlate signature Reference with processed content
CRITICAL. Both major Go SAML libraries (crewjam/saml and gosaml2) have had variants of this vulnerability. The SP must:
- Count Assertion elements and reject if more than expected
- Verify each Assertion's signature individually
- Correlate signed content with processed content by ID
Note: gosaml2 issue #219 (open as of 2025) reports that "Assertions signature is not verified when the response is signed" - a behavioral regression that could enable this attack class.
Various techniques to bypass XML digital signature validation in SAML, including:
- Removing the signature entirely and hoping the SP doesn't require it
- Modifying signed content and re-signing with attacker-controlled key
- Exploiting C14N (canonicalization) algorithm differences
- Using embedded public keys (KeyValue) instead of trusted certificates
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2020-15216 | gosaml2/goxmldsig | Critical | 2020 |
| CVE-2023-48703 | RobotsAndPencils/go-saml | High (7.5) | 2023 |
| CVE-2020-36563 | RobotsAndPencils/go-saml | Moderate | 2020 |
| CVE-2025-66475 | onelogin/php-saml (xmlseclibs) | Critical | 2025 |
| CVE-2020-5390 | PySAML2 | High | 2020 |
| CVE-2021-21238 | PySAML2 | Moderate | 2021 |
Affected versions < v0.5.0. Given a valid SAML Response, an attacker could modify the document, bypassing signature validation to pass off the altered document as signed. This enabled users accessing accounts other than the one authenticated, or full authentication bypass with an expired signed SAML Response.
RobotsAndPencils/go-saml calls xmlsec1 without --enabled-key-data restriction, allowing an attacker to sign SAML assertions themselves and embed the public key directly in the token. The library is archived with no fix planned - users must migrate.
RobotsAndPencils/go-saml uses SHA-1 for XML digital signatures, which is cryptographically weak and allows potential signature forgery.
- Always require signatures (never accept unsigned responses/assertions)
- Only accept signatures from trusted certificates (not embedded KeyValue)
- Use strong hash algorithms (SHA-256 or better, never SHA-1)
- Validate the certificate chain against known IdP certificates
- Validate the C14N algorithm used matches expected values
CRITICAL. gosaml2 itself has had this exact vulnerability. The SP must:
- Always require and verify signatures
- Only trust pre-configured IdP certificates
- Use SHA-256+ for digest/signature algorithms
- Validate the entire signature chain properly
An attacker captures a valid, signed SAML assertion and replays it to gain unauthorized access. This works when the SP doesn't track which assertions have already been consumed.
| CVE | Library/Product | Severity | Year |
|---|---|---|---|
| CVE-2025-64131 | Jenkins SAML Plugin | High | 2025 |
| CVE-2024-5249 | Akana API Platform | Moderate | 2024 |
| CVE-2021-41030 | FortiClient EMS | Critical | 2021 |
| CVE-2022-44457 | Mendix SAML Module | Critical | 2022 |
| CVE-2022-37011 | Mendix SAML Module | Critical | 2022 |
| CVE-2018-14637 | Keycloak | High | 2018 |
| CVE-2017-1000452 | samlify (npm) | High | 2017 |
SAML assertions include temporal validity conditions (NotBefore, NotOnOrAfter) and may include a unique InResponseTo identifier. Replay attacks succeed when:
- The SP doesn't maintain a replay cache of consumed assertion IDs
- The SP doesn't validate
NotOnOrAfter/NotBeforetemporal conditions - The SP doesn't validate
InResponseTocorrelation with pending requests
The Jenkins SAML Plugin did not implement a replay cache, allowing captured SAML assertions to be reused.
- Maintain a replay cache: Store consumed Assertion IDs and reject duplicates
- Validate temporal conditions: Check
NotBeforeandNotOnOrAfterwith appropriate clock skew tolerance - Validate
InResponseTo: Correlate responses with pending authentication requests - Validate
Destination: Ensure the response is intended for this SP's ACS URL - Use short validity windows: Minimize the replay window
HIGH. gosaml2 does validate temporal conditions (NotBefore/NotOnOrAfter), but:
- Replay cache must be implemented by the application (not built into gosaml2)
InResponseTovalidation must be implemented by the application- Clock skew tolerance must be reasonable (not too large)
DoS attacks against SAML implementations, including decompression bombs, XML entity expansion (Billion Laughs), and resource exhaustion.
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2023-26483 | gosaml2 | Moderate (5.3) | 2023 |
| CVE-2020-7731 | gosaml2/goxmldsig | High (7.5) | 2020 |
| CVE-2025-54572 | ruby-saml | Moderate | 2025 |
| CVE-2025-46784 | Lasso (Entrouvert) | Critical | 2025 |
| CVE-2025-46705 | Lasso (Entrouvert) | Critical | 2025 |
| CVE-2025-46404 | Lasso (Entrouvert) | Critical | 2025 |
SAML SPs using gosaml2 were susceptible to DoS via a crafted deflate-compressed request that consumes significantly more memory than the original request size. Maximum deflate compression ratio is 1032:1. Fixed in v0.9.0.
In versions prior to v0.7.0, an attacker could supply an invalid assertion triggering a panic from nil-pointer dereference. Fixed in v0.7.0.
- Limit decompressed size: Set maximum size for deflate-decoded SAML messages
- Disable DTD processing: Prevent XML entity expansion attacks
- Handle panics: Use recover() for nil-pointer dereferences
- Rate limit: Throttle SAML endpoint requests
- Set timeouts: Limit XML parsing time
MODERATE-HIGH. gosaml2 has had both of these vulnerabilities. Ensure:
- Using gosaml2 v0.9.0+ (decompression bomb fix)
- Using gosaml2 v0.7.0+ (nil-pointer fix)
- Application-level rate limiting on SAML endpoints
- Memory limits and timeouts
A modern evolution of Attack Classes 2 and 3. Different XML parsers (or different stages of the same parser) interpret XML namespaces differently, allowing attackers to craft documents where the signature covers one interpretation while the SP processes another.
| CVE | Library | Severity | Year |
|---|---|---|---|
| CVE-2025-66567 | ruby-saml | Critical | 2025 |
| CVE-2025-66568 | ruby-saml | Critical | 2025 |
| CVE-2025-25291 | ruby-saml | Critical | 2025 |
| CVE-2025-25292 | ruby-saml | Critical | 2025 |
| CVE-2026-3047 | Keycloak SAML | High | 2026 |
- Parser differential: Two different XML parsers (e.g., REXML and Nokogiri in ruby-saml) interpret the same XML differently
- Namespace confusion: Prefixed namespaces can cause elements to be interpreted as being in different namespaces during different parsing stages
- C14N differences: Different canonicalization implementations produce different canonical forms
HIGH. Go's encoding/xml has known namespace handling issues (CVE-2020-29509/29510/29511). Any Go SAML implementation using encoding/xml for parsing must validate XML roundtrip stability. Using etree or other XML libraries for parsing while using encoding/xml for deserialization creates a parser differential risk.
| CVE | Library/Product | Severity | Year |
|---|---|---|---|
| CVE-2026-27982 | django-allauth | Moderate | 2026 |
| CVE-2026-22032 | Directus SAML | Moderate | 2026 |
| CVE-2020-12283 | Sourcegraph | Moderate | 2020 |
| CVE-2025-0126 | GlobalProtect | High | 2025 |
| CVE-2019-11881 | Rancher | Moderate | 2019 |
| CVE-2026-29191 | ZITADEL | Critical (XSS in SAML-post) | 2026 |
- Validate
RelayStateURLs against a whitelist of allowed destinations - Don't include user-controlled data in SAML POST forms without sanitization
- Validate
Destinationattribute in SAML responses - Implement CSRF protection on SAML endpoints
| Advisory | CVE | Severity | Fixed In | Description |
|---|---|---|---|---|
| GHSA-5684-g483-2249 | CVE-2020-15216 | Critical | v0.5.0 | Signature Validation Bypass - attacker can modify signed document |
| GHSA-xhqq-x44f-9fgg | CVE-2020-29509 | Critical | v0.6.0 | Authentication Bypass - Go encoding/xml round-trip mutations |
| GHSA-prjq-f4q3-fvfr | CVE-2020-7731 | High | v0.7.0 | Nil-pointer dereference DoS via invalid assertion |
| GHSA-6gc3-crp7-25w5 | CVE-2023-26483 | Moderate | v0.9.0 | DoS via deflate decompression bomb |
| Issue #219 | N/A | Potential High | OPEN | Assertion signature not verified when Response is signed |
| CVE | Severity | Fixed In | Description |
|---|---|---|---|
| CVE-2020-27846 | Critical (9.8) | v0.4.3 | Signature verification bypass via Go encoding/xml |
| CVE-2022-41912 | Critical (9.8) | v0.4.9 | Auth bypass via multiple Assertion elements |
| CVE | Severity | Fixed In | Description |
|---|---|---|---|
| CVE-2023-48703 | High (7.5) | NONE (archived) | Auth bypass - xmlsec1 trusts embedded keys |
| CVE-2020-36563 | Moderate | NONE (archived) | SHA-1 signatures (cryptographically weak) |
Note: RobotsAndPencils/go-saml is archived and unmaintained. Migration away is required.
-
XML Roundtrip Validation: Use
github.com/mattermost/xml-roundtrip-validatorto reject XML documents that mutate during encoding/xml round-trips -
Strict Signature Verification:
- Always require signatures on Responses AND/OR Assertions (configurable)
- Verify the signature covers exactly the element being processed
- Validate Reference URI matches the Assertion/Response ID
- Only trust pre-configured IdP certificates (never embedded KeyValue)
- Use SHA-256+ for digest and signature algorithms
-
Anti-XSW Protections:
- Reject documents with multiple Assertions (unless each is individually verified)
- Ensure the signed Assertion is the one being processed (ID correlation)
- Validate the signed element is in the expected position in the document tree
-
Temporal Validation:
- Validate
NotBeforeandNotOnOrAfterconditions - Use reasonable clock skew tolerance (e.g., 30-90 seconds)
- Validate
Conditionselement fully
- Validate
-
Replay Prevention:
- Maintain a replay cache of consumed Assertion IDs / Response IDs
- Validate
InResponseToagainst pending authentication requests (for SP-initiated flows)
-
Input Validation:
- Limit decompressed message size (deflate bomb protection)
- Validate
Destinationattribute matches the SP's ACS URL - Validate
Audiencerestriction matches the SP's Entity ID - Reject unexpected XML structures (DTDs, entity declarations)
-
Robust Error Handling:
- Handle nil pointers and panics gracefully
- Never return detailed error messages to clients
- Log security-relevant events
- Single-parse architecture: Parse XML once and use the same parsed representation for both signature verification and data extraction
- Avoid re-serialization: Never round-trip XML through encoder/decoder between verification and extraction
- Use etree consistently: If using
etreefor XML manipulation, use it for all operations - Defense in depth: Layer multiple validation checks; don't rely on a single check
- GitHub Advisory Database: https://github.com/advisories?query=saml
- NVD: https://nvd.nist.gov/vuln/search
- CERT/CC VU#475445: https://www.kb.cert.org/vuls/id/475445
- Mattermost Go XML Disclosure: https://mattermost.com/blog/coordinated-disclosure-go-xml-vulnerabilities/
- Mattermost XML Roundtrip Validator: https://github.com/mattermost/xml-roundtrip-validator
- gosaml2 Security Advisories: https://github.com/russellhaering/gosaml2/security/advisories
- crewjam/saml Security Advisories: https://github.com/crewjam/saml/security/advisories
- gosaml2 Issue #219 (open): russellhaering/gosaml2#219