SAML Authentication Attacks: Signature Wrapping, Comment Injection & Assertion Tampering (2025)
SAML (Security Assertion Markup Language) is the workhorse of enterprise single sign-on. When you log into Salesforce, Workday, or an internal app through Okta, Azure AD, or Ping, you are almost certainly riding a SAML 2.0 flow. The protocol's security rests on a single, fragile assumption: that an XML digital signature produced by the Identity Provider (IdP) cannot be tampered with by anyone in the middle — including the user's own browser, which relays the assertion. Break that assumption and you can impersonate any user, including administrators, with no password.
This guide walks through the attack surface a pentester actually exploits during an authorized engagement: XML Signature Wrapping (XSW), comment injection in canonicalization, IdP/SP confusion, and raw assertion tampering against weakly-validating Service Providers (SPs). Every technique here should only ever be run against systems you have written permission to test. The goal is to understand why these flaws keep recurring so you can find them quickly and report them precisely.
How the SAML Web Browser SSO Flow Works
Before attacking it, you need a clear mental model. In the most common flow (SP-initiated, HTTP-POST binding) the browser is the courier between two servers that never talk directly:
The critical detail is step 5: the signed SAMLResponse passes through the attacker's browser as a base64-encoded form field. The attacker can decode it, read it, modify it, and re-encode it before forwarding. The only thing stopping forgery is the SP correctly validating the IdP's signature over the exact bytes that matter. Almost every SAML vulnerability is a failure in that validation step.
A typical decoded response looks like this — note that both the outer Response and the inner Assertion can carry their own Signature element:
<samlp:Response ID="_r1" ...>
<saml:Assertion ID="_a1">
<ds:Signature>...<ds:Reference URI="#_a1"/>...</ds:Signature>
<saml:Subject>
<saml:NameID>[email protected]</saml:NameID>
</saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>user</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
XML Signature Wrapping (XSW)
Signature Wrapping is the flagship SAML attack. It exploits a separation of concerns that goes wrong: the signature-verification code and the assertion-processing code look at different elements in the same document. The verifier confirms the signature over the original, legitimate assertion; the application logic then reads attributes from a second, attacker-injected assertion that was never signed.
The root cause is usually one of two things. Either the SP locates the signed element by a reckless XPath like //Assertion (which matches multiple nodes), or it dereferences the Reference URI to verify the signature but then passes the whole document to a separate parser that picks a different node by document order. ID attributes that are not registered as XML ID type compound the problem because getElementById-style lookups fail to disambiguate.
There are several published XSW permutations (XSW1 through XSW8). A representative payload keeps the original signed assertion intact but smuggles a forged copy where the parser will actually read it:
<samlp:Response ID="_r1">
<!-- forged assertion the APP reads; no valid signature -->
<saml:Assertion ID="_evil">
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
<saml:AttributeStatement>
<saml:Attribute Name="role">
<saml:AttributeValue>admin</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
<!-- original signed assertion the VERIFIER checks -->
<saml:Assertion ID="_a1">
<ds:Signature>...<ds:Reference URI="#_a1"/>...</ds:Signature>
<saml:Subject><saml:NameID>[email protected]</saml:NameID></saml:Subject>
</saml:Assertion>
</samlp:Response>
The verifier finds #_a1, validates the signature over Alice's real assertion, and returns "signature OK." The application's attribute reader, however, grabs the first Assertion in document order — the forged one — and logs you in as admin. To test this systematically you mutate node placement: wrap the forged assertion inside the original, hide the original inside an Extensions or Object element, or duplicate IDs so the lookup resolves to the wrong node. Tools like SAML Raider (a Burp extension) automate all eight permutations; the SAML Analyzer is useful for decoding, pretty-printing, and inspecting the signed-vs-read element boundaries before you craft each variant by hand.
Comment Injection and Canonicalization Abuse
Comment injection (the 2018 "Duo/CVE-2017-11427-class" family of bugs) is more subtle and does not even require touching the signature. It abuses a mismatch between the XML canonicalization that the signature covers and the text-extraction routine the SP uses to read the NameID.
Many SP libraries extract a node's text by reading only the first text child of an element, or by calling a DOM accessor that stops at the first comment. If you inject an XML comment into the middle of a signed NameID, the signature still validates — canonicalization normalizes comments away or the signed digest is unaffected — but the application reads a truncated or altered string:
<saml:NameID>[email protected]<!--injected-->.attacker.test</saml:NameID>
Depending on how the SP concatenates text nodes, this can be read as [email protected] while the user the IdP actually authenticated was [email protected] — a domain the attacker controls. The signature is over the full, legitimate string; the parser sees only the part before the comment. A single registered low-privilege account at the attacker-controlled subdomain becomes any account on the target. When testing, try comment placement at every boundary in the NameID and in any attribute used for authorization, and compare what the IdP signed against what the SP appears to honor.
IdP/SP Confusion and Audience Restriction Bypass
Assertions are scoped. The <saml:Conditions> element carries an <AudienceRestriction> naming the intended SP, and NotBefore/NotOnOrAfter timestamps bound validity. An SP that validates the signature but ignores the audience will accept an assertion minted for a different SP — classic token confusion.
This matters in two scenarios. First, organizations that run multiple SPs behind one IdP: capture a valid assertion from a low-value app (a wiki, a status page) and replay it to a high-value app (an admin console) that shares the IdP but fails to enforce its own EntityID as the audience. Second, multi-tenant IdPs: if the SP does not pin the expected Issuer and signing certificate together, an attacker can present an assertion signed by a different, attacker-controlled IdP whose certificate the SP wrongly accepts. Always check these conditions during testing:
- Audience scope: Does the SP reject an assertion whose
<Audience>names a different SP? Swap it and see. - Issuer/cert binding: Is the signing certificate validated against the configured IdP for that exact
Issuer, or is any trusted cert accepted? - Recipient and Destination: Is
SubjectConfirmationData/@RecipientandResponse/@Destinationchecked against the SP's own ACS URL? - Replay window: Are
NotOnOrAfterand a one-time-useInResponseToenforced, or can you replay the same assertion repeatedly?
Assertion Tampering Against Weak Validators
The simplest attack of all still works on a surprising number of homegrown integrations: the SP either does not verify the signature at all, accepts SignatureMethod set to none, or trusts the assertion when the <ds:Signature> element is simply deleted. This is the SAML cousin of the JWT alg=none flaw. The methodology is to strip or break the signature progressively and observe what the SP accepts:
# 1. Remove the <ds:Signature> element entirely
# 2. Keep Signature but edit a single byte in SignatureValue
# 3. Swap the embedded certificate for your own and re-sign
# 4. Change NameID / role attribute and replay unsigned
If any of those produces an authenticated session, you have a full authentication bypass. Re-encode the edited XML (deflate + base64 for redirect binding, plain base64 for POST binding) and resubmit. Because SAML and JWT-based SSO often coexist in the same estate, it is worth pairing this work with the broader OAuth and OIDC cheat sheet — many modern apps fall back to OIDC, and the same "validate the token, not just decode it" lesson applies. For a structured, step-by-step methodology you can follow on an engagement, see the guide on testing for SAML vulnerabilities.
Defenses and Remediation
The defenses are well understood; the failures come from custom code and misconfiguration, not from the spec being impossible to implement safely. When you write up findings, recommend the following concrete controls:
- Verify before you parse, and parse what you verified. The signed element and the element you read attributes from must be the same node. Use a library that returns the verified subtree and operate only on that — never re-query the document independently.
- Reject documents with multiple assertions or duplicate IDs. Enforce schema validation, register ID attributes as XML
IDtype, and treat any ambiguity as a hard failure. This kills most XSW permutations. - Use exclusive canonicalization and reject comments in security-relevant nodes. Read
NameIDand attribute values with a routine that concatenates all text nodes, defeating comment-injection truncation. - Pin the Issuer to its certificate. Maintain an explicit allow-list mapping each trusted
Issuerto its exact signing certificate, and never accept assertions signed by any other key. - Enforce all conditions. Validate
AudienceRestrictionagainst your ownEntityID, checkRecipient/Destinationagainst your ACS URL, enforce the validity window, and prevent replay via one-time-useInResponseTotracking. - Reject unsigned and weakly-signed responses. Require a signature, forbid
SignatureMethodof none, and disallow SHA-1 in favor of SHA-256 or stronger. - Keep your SAML library current. Most of these classes were fixed upstream years ago; the lingering risk is pinned old versions and bespoke parsers.
SAML is not inherently insecure — but it is unforgiving of shortcuts. As a tester, the highest-yield habit is to always compare two things side by side: the bytes the IdP actually signed, and the values the SP ultimately trusts. Wherever those two diverge, there is a finding. Decode every assertion, mutate it methodically, and document the exact validation gap so the engineering team can close it cleanly.
Level up your security testing
Install the CLI
npx payload-playgroundExplore All Tools
Encoding, hashing, JWT & more
Browse Cheat Sheets
Quick-reference payload guides