summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorIvan Kanakarakis <ivan.kanak@gmail.com>2021-01-09 00:31:13 +0200
committerIvan Kanakarakis <ivan.kanak@gmail.com>2021-01-15 16:09:06 +0200
commit3b707723dcf1bf60677b424aac398c0c3557641d (patch)
tree32be8bf3732a60e3f0697ba9d024cac6d6929374 /src
parentb76ea403e3107bbae73b653215985b9e1f27c5d4 (diff)
downloadpysaml2-3b707723dcf1bf60677b424aac398c0c3557641d.tar.gz
Fix CVE-2021-21238 - SAML XML Signature wrapping
All users of pysaml2 that use the default `CryptoBackendXmlSec1` backend and need to verify signed SAML documents are impacted. `pysaml2 <= 6.4.1` does not validate the SAML document against an XML schema. This allows invalid XML documents to trick the verification process, by presenting elements with a valid signature inside elements whose content has been malformed. The verification is offloaded to `xmlsec1` and `xmlsec1` will not validate every signature in the given document, but only the first it finds in the given scope. Credits for the report: - Victor Schönfelder Garcia (isits AG International School of IT Security) - Juraj Somorovsky (Paderborn University) - Vladislav Mladenov (Ruhr University Bochum) Signed-off-by: Ivan Kanakarakis <ivan.kanak@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/saml2/sigver.py26
-rw-r--r--src/saml2/xml/__init__.py0
-rw-r--r--src/saml2/xml/schema/__init__.py74
3 files changed, 100 insertions, 0 deletions
diff --git a/src/saml2/sigver.py b/src/saml2/sigver.py
index 0e8f1942..4e45491b 100644
--- a/src/saml2/sigver.py
+++ b/src/saml2/sigver.py
@@ -51,6 +51,8 @@ from saml2.xmlenc import EncryptedKey
from saml2.xmlenc import CipherData
from saml2.xmlenc import CipherValue
from saml2.xmlenc import EncryptedData
+from saml2.xml.schema import node_to_schema
+from saml2.xml.schema import XMLSchemaError
logger = logging.getLogger(__name__)
@@ -1460,6 +1462,30 @@ class SecurityContext(object):
if not certs:
raise MissingKey(_issuer)
+ # validate XML with the appropriate schema
+ try:
+ _schema = node_to_schema[node_name]
+ except KeyError as e:
+ error_context = {
+ "message": "Signature verification failed. Unknown node type.",
+ "issuer": _issuer,
+ "type": node_name,
+ "document": decoded_xml,
+ }
+ raise SignatureError(error_context) from e
+
+ try:
+ _schema.validate(str(item))
+ except XMLSchemaError as e:
+ error_context = {
+ "message": "Signature verification failed. Invalid document format.",
+ "ID": item.id,
+ "issuer": _issuer,
+ "type": node_name,
+ "document": decoded_xml,
+ }
+ raise SignatureError(error_context) from e
+
# saml-core section "5.4 XML Signature Profile" defines constrains on the
# xmldsig-core facilities. It explicitly dictates that enveloped signatures
# are the only signatures allowed. This means that:
diff --git a/src/saml2/xml/__init__.py b/src/saml2/xml/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/saml2/xml/__init__.py
diff --git a/src/saml2/xml/schema/__init__.py b/src/saml2/xml/schema/__init__.py
new file mode 100644
index 00000000..56e08b1c
--- /dev/null
+++ b/src/saml2/xml/schema/__init__.py
@@ -0,0 +1,74 @@
+from importlib_resources import path as _resource_path
+
+from xmlschema import XMLSchema as _XMLSchema
+from xmlschema.exceptions import XMLSchemaException as XMLSchemaError
+
+import saml2.data.schemas as _data_schemas
+
+
+def _create_xml_schema_validator(source, **kwargs):
+ kwargs = {
+ **kwargs,
+ "validation": "strict",
+ "locations": _locations,
+ "base_url": source,
+ "allow": "sandbox",
+ "use_fallback": False,
+ }
+ return _XMLSchema(source, **kwargs)
+
+
+with _resource_path(_data_schemas, "xml.xsd") as fp:
+ _path_schema_xml = str(fp)
+with _resource_path(_data_schemas, "envelope.xsd") as fp:
+ _path_schema_envelope = str(fp)
+with _resource_path(_data_schemas, "xenc-schema.xsd") as fp:
+ _path_schema_xenc = str(fp)
+with _resource_path(_data_schemas, "xmldsig-core-schema.xsd") as fp:
+ _path_schema_xmldsig_core = str(fp)
+with _resource_path(_data_schemas, "saml-schema-assertion-2.0.xsd") as fp:
+ _path_schema_saml_assertion = str(fp)
+with _resource_path(_data_schemas, "saml-schema-metadata-2.0.xsd") as fp:
+ _path_schema_saml_metadata = str(fp)
+with _resource_path(_data_schemas, "saml-schema-protocol-2.0.xsd") as fp:
+ _path_schema_saml_protocol = str(fp)
+
+_locations = {
+ "http://www.w3.org/XML/1998/namespace": _path_schema_xml,
+ "http://schemas.xmlsoap.org/soap/envelope/": _path_schema_envelope,
+ "http://www.w3.org/2001/04/xmlenc#": _path_schema_xenc,
+ "http://www.w3.org/2000/09/xmldsig#": _path_schema_xmldsig_core,
+ "urn:oasis:names:tc:SAML:2.0:assertion": _path_schema_saml_assertion,
+ "urn:oasis:names:tc:SAML:2.0:protocol": _path_schema_saml_protocol,
+}
+
+with _resource_path(_data_schemas, "saml-schema-assertion-2.0.xsd") as fp:
+ schema_saml_assertion = _create_xml_schema_validator(str(fp))
+with _resource_path(_data_schemas, "saml-schema-metadata-2.0.xsd") as fp:
+ schema_saml_metadata = _create_xml_schema_validator(str(fp))
+with _resource_path(_data_schemas, "saml-schema-protocol-2.0.xsd") as fp:
+ schema_saml_protocol = _create_xml_schema_validator(str(fp))
+
+
+node_to_schema = {
+ # AssertionType
+ "urn:oasis:names:tc:SAML:2.0:assertion:Assertion": schema_saml_assertion,
+ # EntitiesDescriptorType
+ "urn:oasis:names:tc:SAML:2.0:metadata:EntitiesDescriptor": schema_saml_metadata,
+ # EntityDescriptorType
+ "urn:oasis:names:tc:SAML:2.0:metadata:EntityDescriptor": schema_saml_metadata,
+ # RequestAbstractType
+ "urn:oasis:names:tc:SAML:2.0:protocol:AssertionIDRequest": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:SubjectQuery": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:AuthnRequest": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:ArtifactResolve": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:ManageNameIDRequest": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:LogoutRequest": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:NameIDMappingRequest": schema_saml_protocol,
+ # StatusResponseType
+ "urn:oasis:names:tc:SAML:2.0:protocol:Response": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:ArtifactResponse": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:ManageNameIDResponse": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:LogoutResponse": schema_saml_protocol,
+ "urn:oasis:names:tc:SAML:2.0:protocol:NameIDMappingResponse": schema_saml_protocol,
+}