SAML's signature problem: It’s not you, it’s XML
A deep dive into the messy world of SAML signature verification bugs — complete with real examples, cautionary tales, and practical tips to keep your app out of trouble.
If you’ve spent any time in the SAML trenches, you know the protocol is full of footguns. But few are as deep — or as historically broken — as XML Digital Signatures (XML DSIG). All engineers who have spent time working with SAML have had their fair share of “wait, this is legal XML?” moments.
While XML DSIG is designed to ensure the integrity and authenticity of SAML messages, the reality is that signature validation in SAML is fragile — and frequently mishandled. These issues aren’t theoretical: they've led to real-world vulnerabilities in widely used libraries, and they will continue to crop up in audits, pen tests, and coordinated disclosures.
This post takes a technical look at why signature validation in SAML is hard to get right — and how common libraries (especially in JavaScript and Node.js) often miss critical checks. We'll also look at hands-on examples that show where things can go wrong, and how to do it right.
XML Digital Signatures 101
XML DSIG allows you to cryptographically sign parts of an XML document. In SAML, that usually means signing assertions or metadata to ensure the content hasn’t been tampered with.
At a high level, an XML Signature does two things:
- Identifies what part of the document is being signed (via a reference like
#abc123
) - Verifies that the data under that reference hasn’t been tampered with
In SAML, this usually means signing an <Assertion>
or the full <Response>
. The signature sits outside the signed content and includes a pointer (typically via a URI fragment) to the element being signed.
A SAML response usually looks like this:
The <ds:Reference URI="#abc123" />
tells the verifier which element in the document was signed — in this case, the Assertion with ID abc123
.
But that reference model is also the source of the problem.
Let’s break down the common issues — with examples.
Problem 1: Reference confusion
This happens when the signature verification step itself is broken.
Signatures often include references like URI="#abc123"
, which point to elements with a matching ID
. But XML allows multiple elements with IDs, or no ID at all — and libraries often pick the “first matching element,” which may not be the one the developer (or the signature) intended. If the application consumes a different element than the one that was signed, that’s a problem.
Let’s say the attacker sends this:
This is what a vulnerable validation looks like in JS pseudocode:
The fix here is to make sure:
- The ID actually exists and points to a valid
Assertion
element. - It's unique.
- You're only parsing data that was inside the verified reference.
Problem 2: Canonicalization mismatch
XML supports multiple equivalent representations of the same data — differences in whitespace, attribute order, and namespace declarations can all change the raw byte output.
To ensure consistent signature verification, XML digital signatures rely on canonicalization (c14n) — a process that normalizes these differences into a standardized format before signing or verifying.
However, if the sender and receiver don't use the same canonicalization method, or if a tool fails to canonicalize correctly, signature verification can still fail. This isn't usually a problem with well-behaved SAML libraries, but it's a subtle and critical part of the overall process.
A signature may validate on one parser and fail on another — or vice versa — depending on how canonicalization is handled. Worse, some libraries skip canonicalization entirely or hardcode a brittle default.
To avoid this problem, the best practice is to always validate using the same canonicalization algorithm that the signer used (typically http://www.w3.org/2001/10/xml-exc-c14n#
).
Problem 3: Signature wrapping (classic attack)
This is the big one. This attack happens when the application uses the wrong part of the XML — even though the signature validator did its job correctly.
In a signature wrapping attack, an attacker injects a validly signed <Assertion>
somewhere in the message, but also adds a second, unsigned assertion that the application ends up using. If the validator checks the signed assertion, but the app parses and uses the unsigned one, the attacker wins — even though the signature technically verified. This category of bug has affected:
- Older versions of
xmlsec
,xmltooling
, andOpenSAML
- Node.js libraries like
xml-crypto
,samlify
, and others - Java and .NET SAML implementations as well
Example:
Here’s what happens:
- The validator checks the signature → ✅ it points to
#good123
- But the app does something like:
doc.getElementsByTagName("Assertion")[0];
- That returns the unsigned assertion (because it's first in the XML).
- So the app uses attacker-controlled data — even though the signature was valid.
The app logic is the issue. It uses data that wasn’t signed.
This is what the vulnerable validation looks like:
This is what the best practice looks like:
Problem 4: Broken or missing schema validation
Some XML libraries don't enforce strict schema validation, which can allow malformed or unexpected structures to slip through — potentially making certain attacks easier.
However, enabling schema validation isn't a silver bullet either. In some cases, especially when DTDs or external entities are allowed, schema validation can itself introduce vulnerabilities, like XML External Entity (XXE) attacks or denial-of-service through maliciously crafted payloads (e.g., oversized DTDs).
In SAML implementations, the key is to strike a careful balance: validate enough to reject malformed input, but disable dangerous features like external entity resolution.
How to validate SAML assertions securely
Here’s a checklist of what correct signature validation should include:
- Strict reference validation
- Resolve the
URI
in<ds:Reference URI="#...">
exactly. - Ensure the
ID
attribute is unique and actually on the target element.
- Resolve the
- Canonicalization awareness
- Don’t assume defaults.
- Validate with the canonicalization algorithm specified in the signature (
ds:CanonicalizationMethod
). - Beware of libraries that hardcode this or apply it inconsistently.
- Schema validation (yes, really): Validate the SAML document against its schema before signature verification. This prevents injection of invalid elements or unexpected document structure.
- Tie verification to usage
- After verifying the signature, extract only the signed data.
- Don’t query the document globally or fall back to
getElementsByTagName
. That’s how bypasses happen. Instead only parse values from elements you just verified.
- Use a trusted library — but check it: Libraries like
xmlsec1
,OneLogin’s python-saml
, andjava-saml
are popular, but not immune to these problems. Audit for how they handle ID resolution, XPath queries, and canonicalization. - Stay up to date: Watch for library updates and security advisories — many of these bugs are patched quietly.
Looking ahead
SAML isn’t going away anytime soon — and neither is XML DSIG. Despite the rise of OIDC and modern identity protocols, SAML remains deeply embedded in enterprise identity, especially in B2B and federated auth scenarios.
If you're building or maintaining SAML integrations, it's not enough to understand the spec. You need to understand how the spec breaks in practice — where libraries cut corners, how common misuses arise, and how subtle differences in XML parsing or signature handling can become exploitable paths.
We’ll continue publishing content that demystifies these internals: deep dives into how signature validation really works, walkthroughs of common misconfigurations, and breakdowns of real-world bugs. If you're trying to build secure, standards-compliant SAML infrastructure (or just trying not to shoot yourself in the foot), stay tuned — there's more to come.