Context
XML Signature Wrapping (XSW) is the most common real-world break of SAML SP implementations: an attacker takes a legitimately signed assertion, wraps or relocates it in the document, and injects a second, attacker-controlled assertion that the consuming code reads — while signature verification still succeeds against the original signed node.
ExSaml.Core.Sp extracts the assertion via XPath and then calls Dsig.verify/2. We need to guarantee the invariant: the node whose signature was verified is exactly the node whose contents are consumed (subject, NameID, attributes, conditions). If verification and extraction can ever resolve to different nodes, the SP is exploitable.
Goal
- Review the response/assertion consumption path (
Core.Sp, Core.Xml.Dsig) and make the "verified node == consumed node" binding explicit and enforced (not incidental to XPath ordering).
- Add a dedicated XSW test corpus that fails closed.
Proposed scope
- Audit
decrypt/extract/verify ordering in Core.Sp for:
- response-level signature vs assertion-level signature (and both present),
- multiple
saml:Assertion elements in one response,
- signed element relocated under a different parent (classic XSW),
Reference URI pointing to an Id that is not the consumed element,
- duplicated
Id attributes.
- Bind digest/reference resolution to the concrete consumed node rather than re-querying the document.
- Test fixtures covering each wrapping variant above, each asserting rejection (
:bad_signature / :bad_digest / a dedicated reason).
Why
This is the single highest-value SAML security check after "verify is actually cryptographic" (already satisfied — Dsig.verify/2 calls :public_key.verify and binds trust to configured IdP fingerprints). Without explicit XSW defenses, a valid signature on a benign assertion can authorise a forged one.
Out of scope
- General XXE / c14n corpus (tracked separately).
Relates to #32.
Context
XML Signature Wrapping (XSW) is the most common real-world break of SAML SP implementations: an attacker takes a legitimately signed assertion, wraps or relocates it in the document, and injects a second, attacker-controlled assertion that the consuming code reads — while signature verification still succeeds against the original signed node.
ExSaml.Core.Spextracts the assertion via XPath and then callsDsig.verify/2. We need to guarantee the invariant: the node whose signature was verified is exactly the node whose contents are consumed (subject, NameID, attributes, conditions). If verification and extraction can ever resolve to different nodes, the SP is exploitable.Goal
Core.Sp,Core.Xml.Dsig) and make the "verified node == consumed node" binding explicit and enforced (not incidental to XPath ordering).Proposed scope
decrypt/extract/verify ordering inCore.Spfor:saml:Assertionelements in one response,Reference URIpointing to anIdthat is not the consumed element,Idattributes.:bad_signature/:bad_digest/ a dedicated reason).Why
This is the single highest-value SAML security check after "verify is actually cryptographic" (already satisfied —
Dsig.verify/2calls:public_key.verifyand binds trust to configured IdP fingerprints). Without explicit XSW defenses, a valid signature on a benign assertion can authorise a forged one.Out of scope
Relates to #32.