SAML signature wrapping is an attack on single sign on that turns a valid signature into a lie about who you are. The identity provider signs an XML assertion that says “this user is alice.” The attacker captures that signed assertion and rearranges the document so the signature still checks out over the original element, while the service provider reads a second, injected assertion that says “this user is admin.” The signature is valid. The thing the application uses is not the thing that was signed. This post explains SAML signature wrapping from the ground up, shows the shape of a wrapped document, and lists the defenses that actually close it.
How SAML single sign on works
SAML is the protocol that lets you log in to one place and reach many apps without typing a password at each one. Three parties take part. The user in a browser, the service provider (the app you want to use, call it acme.example), and the identity provider (the trusted login system that vouches for who you are).
The flow is short. You hit acme.example. It does not know you, so it bounces your browser to the identity provider. You authenticate there. The identity provider builds an XML document called an assertion that states your identity and signs it with an XML digital signature. Your browser carries that signed assertion back to acme.example. The service provider checks the signature, sees it was issued by a provider it trusts, and logs you in as whoever the assertion names.
A trimmed assertion looks like this. The Assertion element carries an ID, and the Signature block points at that ID with a Reference, saying “I cover the element whose id is _abc123.”
<Response>
<Assertion ID="_abc123">
<Subject><NameID>alice@acme.example</NameID></Subject>
<Signature>
<Reference URI="#_abc123"/>
<SignatureValue>...</SignatureValue>
</Signature>
</Assertion>
</Response>
Why a valid signature is not enough
Here is the gap that SAML signature wrapping lives in. Two separate pieces of code look at this document, and nothing forces them to agree on which element they are looking at.
The first piece is the signature verifier. It reads the Reference URI="#_abc123", walks the tree to find the element with that id, runs the math, and reports “the signature is valid.” The second piece is the business logic that pulls out the identity. It often does something looser, like “find the first Assertion under Response and read its NameID.” If those two pieces resolve to different elements, you have a problem. The verifier blesses one node. The application trusts a different node. Neither one notices.
A valid signature only proves that some element in the document was signed. It does not prove that the element you read is that element.
This is the same family of trust mistake we cover in authentication vs authorization, where proving who someone is gets quietly confused with deciding what they may do. It also rhymes with XXE injection, another case where an XML parser does more, or reads more, than the developer assumed. The XML is trusted as plain data when it is really a set of instructions.
The wrapping trick at a structural level
The attacker starts with a real, validly signed assertion captured during their own legitimate login. They cannot forge the signature, and they do not try. Instead they rebuild the document around it.
The move has two parts. First, take the signed Assertion with id _abc123 and tuck it somewhere the signature verifier will still find it by id, but the business logic will skip. A common hiding spot is inside a wrapper element, or deeper in the tree. Second, inject a brand new Assertion, unsigned, carrying the attacker’s chosen identity, and place it where the business logic looks first.
The shape of a wrapped document, with placeholder elements, looks like this. The signed original is moved aside. The injected one sits up front.
<Response>
<!-- injected, UNSIGNED, attacker controlled -->
<Assertion ID="_evil999">
<Subject><NameID>admin@acme.example</NameID></Subject>
</Assertion>
<!-- relocated original, still validly signed -->
<Wrapper>
<Assertion ID="_abc123">
<Subject><NameID>alice@acme.example</NameID></Subject>
<Signature>
<Reference URI="#_abc123"/>
<SignatureValue>...unchanged...</SignatureValue>
</Signature>
</Assertion>
</Wrapper>
</Response>
Now read it the way each side reads it. The verifier follows URI="#_abc123", finds the relocated original inside Wrapper, checks the math over alice’s assertion, and says “valid.” The business logic asks for the first Assertion under Response, lands on _evil999, and reads admin@acme.example. The result is authentication bypass or full impersonation, with a signature that genuinely validates.
There are many variants. The signed element can be hidden, duplicated, or nested at a different depth, and the injected element can be placed before, after, or as a sibling, depending on exactly how the consuming code selects its node. The principle behind all of them is the same. XML signature wrapping is a well studied class from academic research, and the original work catalogued a whole tree of these rearrangements. The lesson held up. If the verifier and the consumer can disagree about which element is in play, an attacker will engineer that disagreement.
Detecting and preventing SAML signature wrapping
The fix is one idea stated several ways. The element you consume must be exactly the element that was signed. Not an element with the same name. Not the first one you find. The same node, resolved by the same reference the signature used.
- Bind consumption to the signed node. After the signature verifies, hold a reference to the precise element it covered, and read your identity only from that node. Do not re run a fresh “find the first assertion” query against the document.
- Reject documents with more than one assertion. A valid login response carries one assertion. If you see two, do not try to pick the right one. Refuse the whole document.
- Mark and check the signed node. Some libraries let you tag the verified element so later code can assert it is reading the marked node, not a look alike sitting elsewhere in the tree.
- Avoid id based reference ambiguity. Wrapping leans on the verifier resolving an id to one node while the parser resolves the same name to another. Validate against a strict schema, reject duplicate ids, and do not let two elements answer to the same identifier.
- Use a hardened, well maintained SAML library. This is not a parser to hand roll. Mature libraries have absorbed years of wrapping reports and apply the position checks for you. Keep them patched.
- Run schema validation before trusting structure. A schema that forbids stray wrapper elements and extra assertions removes many of the hiding spots wrapping needs.
For more reading on the trust boundary side of this, see our work under access control. Wrapping is ultimately an access control failure dressed up as a cryptography success.
Why this slips past review
The dangerous part of SAML signature wrapping is that the signature check passes. Logs show a valid signature from a trusted issuer. The login works for real users every day. The flaw only appears when someone sends a document built so that the verifier and the consumer look at different elements, and that is a question no one usually writes down. It is exactly the kind of assumption an autonomous researcher that tests assumptions, rather than known payloads, is built to probe, by asking whether “the signature is valid” and “the identity I am using was signed” are truly the same claim. You can read more about our approach on the about page.
Frequently asked questions
What is SAML signature wrapping?
SAML signature wrapping is an attack where an attacker takes a validly signed SAML assertion and rearranges the XML so the signature still validates over the original element while the service provider reads a second, injected assertion that carries the attacker’s chosen identity. The signature is genuinely valid, but the element the application uses is not the element that was signed, which leads to authentication bypass or impersonation.
Why does a valid signature not stop the attack?
Because two different pieces of code look at the document. The signature verifier resolves a reference, usually an id, and confirms the math over one element. The business logic separately picks an element to read identity from, often by position or element name. If those two resolve to different nodes, the verifier blesses one assertion while the application trusts another. The signature proves only that some element was signed, not that the element you read is that element.
How do you prevent SAML signature wrapping?
Bind consumption to the exact node that was signed, resolving identity only from the element the signature covered rather than re running a fresh search. Reject any response that contains more than one assertion, reject duplicate ids, and validate against a strict schema. Use a hardened, well maintained SAML library instead of hand rolling verification, and keep it patched. See the OWASP SAML Security Cheat Sheet for implementation guidance: https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html
Is XML signature wrapping a new or theoretical problem?
No. XML signature wrapping is a well studied class first catalogued in academic research, and it maps to the broader weakness of improper verification of a cryptographic signature, tracked as CWE-347 (https://cwe.mitre.org/data/definitions/347.html). The general lesson, that a verifier and a consumer must agree on exactly which element is in play, applies to SAML and to other signed XML protocols.
