-
Notifications
You must be signed in to change notification settings - Fork 446
Description
This problem is two-fold:
SamlBase.register_prefixregisters namespace prefixes but nothing restores the registry to the previous state, so whatever prefixes that get registered remain registered for the process's lifetime.- Verification of SAML messages deserializes the incoming XML into a class, then reserializes it before passing it in for signature validation with the security context. Due to the prefix pollution, the XML changes slightly and ends up failing validation. Note that in my case it's validating
Responses, and the assertions are signed then encrypted, while the outersamlpelements are not signed.- Subproblem, upon further research: it actually doesn't matter whether pollution is happening, ElementTree is dragging all namespaces it can find to the root element. The only reason the response is validating without pollution is because the namespaces outside the encrypted data aren't coinciding with the namespaces inside since the decryption was done by
xmlsec1and not passed through ElementTree.
- Subproblem, upon further research: it actually doesn't matter whether pollution is happening, ElementTree is dragging all namespaces it can find to the root element. The only reason the response is validating without pollution is because the namespaces outside the encrypted data aren't coinciding with the namespaces inside since the decryption was done by
Example overview
Imagine a Django web application with multiple workers and the environment as indicated at the bottom of this issue.
AuthnRequestis generated via djangosaml2- djangosaml2 provides some prefix mappings to make the generated request prettier.
register_prefixis called here and pollutes the worker the request was generated on
- djangosaml2 provides some prefix mappings to make the generated request prettier.
- User is redirected to IdP and signs in
- User receives a
Responseand is redirected back to the application Responseprocessing- If hitting an unpolluted worker: XML passed to
xmlsec1starts with<ns0:Response ...and contains contains all namespace prefixes; however, it validates successfully - If hitting a polluted worker: XML passed to
xmlsec1starts with<samlp:Response ...and contains namespace prefixes forsamlp,saml,ds, andxencin the root element; it fails to validate
- If hitting an unpolluted worker: XML passed to
I'm kind of curious why the reserialized XML is validated instead of the original XML. I'm sure there are reasons for it, but it seems a bit counterintuitive and is subject to serialization shenanigans like this.
I also haven't wrapped my head around how XML canonicalization and signing works, so I can't offer any opinions on whether whatever other processing that has been done by pysaml2 is correct.
Environment
- Python 3.11.11
- Django 4.2.17
- pysaml2 7.5.0 (modified)
- djangosaml2 1.9.3 (plus some overrides)
- xmlsec1 1.2.41
- IdP uses ComponentSpace SAML
I'll check with my development team whether I can publicly attach examples of an original AuthnResponse, post-processed response without pollution, and post-processed response with pollution, and decrypted counterparts.