From 3b707723dcf1bf60677b424aac398c0c3557641d Mon Sep 17 00:00:00 2001 From: Ivan Kanakarakis Date: Sat, 9 Jan 2021 00:31:13 +0200 Subject: Fix CVE-2021-21238 - SAML XML Signature wrapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/saml2/sigver.py | 26 ++++++++++++++ src/saml2/xml/__init__.py | 0 src/saml2/xml/schema/__init__.py | 74 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 src/saml2/xml/__init__.py create mode 100644 src/saml2/xml/schema/__init__.py (limited to 'src') 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 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, +} -- cgit v1.2.1