From 381018227cccc30ef10c213ccfa20043509f86c6 Mon Sep 17 00:00:00 2001 From: Thomas Goirand Date: Mon, 9 Jan 2017 16:50:55 +0100 Subject: [PATCH] XML External Entity attack fix * Add upstream patch for XML External Entity attack (Closes: #850716). Change-Id: I2713c4c1489aae2fe39b890d49e3cebaef3ea7ca --- debian/changelog | 8 +- debian/control | 8 +- debian/patches/fix-xxe-in-xml-parsing.patch | 259 ++++++++++++++++++++ debian/patches/series | 1 + 4 files changed, 272 insertions(+), 4 deletions(-) create mode 100644 debian/patches/fix-xxe-in-xml-parsing.patch create mode 100644 debian/patches/series diff --git a/debian/changelog b/debian/changelog index 5271d97..57b8f77 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,12 @@ -python-pysaml2 (3.0.0-5) UNRELEASED; urgency=medium +python-pysaml2 (3.0.0-5) unstable; urgency=medium + [ Ondřej Nový ] * Bumped debhelper compat version to 10 - -- Ondřej Nový Thu, 24 Nov 2016 00:10:41 +0100 + [ Thomas Goirand ] + * Add upstream patch for XML External Entity attack (Closes: #850716). + + -- Thomas Goirand Mon, 09 Jan 2017 16:28:55 +0100 python-pysaml2 (3.0.0-4) unstable; urgency=medium diff --git a/debian/control b/debian/control index fa92330..ecabc08 100644 --- a/debian/control +++ b/debian/control @@ -14,6 +14,7 @@ Build-Depends-Indep: python-alabaster, python-crypto, python-dateutil, python-decorator, + python-defusedxml, python-mako, python-memcache, python-openssl, @@ -28,6 +29,7 @@ Build-Depends-Indep: python-alabaster, python3-crypto, python3-dateutil, python3-decorator, + python3-defusedxml, python3-mako, python3-memcache, python3-openssl, @@ -47,7 +49,8 @@ Homepage: https://github.com/rohe/pysaml2 Package: python-pysaml2 Architecture: all -Depends: python-mako, +Depends: python-defusedxml, + python-mako, python-memcache, python-pyasn1 (>=0.1.8), python-repoze.who, @@ -72,7 +75,8 @@ Description: SAML Version 2 to be used in a WSGI environment - Python 2.x Package: python3-pysaml2 Architecture: all -Depends: python3-mako, +Depends: python3-defusedxml, + python3-mako, python3-memcache, python3-pyasn1 (>=0.1.8), python3-repoze.who, diff --git a/debian/patches/fix-xxe-in-xml-parsing.patch b/debian/patches/fix-xxe-in-xml-parsing.patch new file mode 100644 index 0000000..a2c7aa5 --- /dev/null +++ b/debian/patches/fix-xxe-in-xml-parsing.patch @@ -0,0 +1,259 @@ +Description: [PATCH] Fix XXE in XML parsing (related to #366) + This fixes XXE issues on anything where pysaml2 parses XML directly as part of + issue #366. It doesn't address the xmlsec issues discussed on that ticket as + they are out of reach of a direct fix and need the underlying library to fix + this issue. +From: Florian Rüchel +Date: Mon, 31 Oct 2016 11:56:48 +1100 +Origin: https://github.com/rohe/pysaml2/commit/6e09a25d9b4b7aa7a506853210a9a14100b8bc9b +Last-Update: 2017-01-09 + +--- python-pysaml2-3.0.0.orig/setup.py ++++ python-pysaml2-3.0.0/setup.py +@@ -31,6 +31,7 @@ install_requires = [ + 'pytz', + 'pyOpenSSL', + 'python-dateutil', ++ 'defusedxml', + 'six' + ] + +--- python-pysaml2-3.0.0.orig/src/saml2/__init__.py ++++ python-pysaml2-3.0.0/src/saml2/__init__.py +@@ -34,6 +34,7 @@ except ImportError: + import cElementTree as ElementTree + except ImportError: + from elementtree import ElementTree ++import defusedxml.ElementTree + + root_logger = logging.getLogger(__name__) + root_logger.level = logging.NOTSET +@@ -85,7 +86,7 @@ def create_class_from_xml_string(target_ + """ + if not isinstance(xml_string, six.binary_type): + xml_string = xml_string.encode('utf-8') +- tree = ElementTree.fromstring(xml_string) ++ tree = defusedxml.ElementTree.fromstring(xml_string) + return create_class_from_element_tree(target_class, tree) + + +@@ -267,7 +268,7 @@ class ExtensionElement(object): + + + def extension_element_from_string(xml_string): +- element_tree = ElementTree.fromstring(xml_string) ++ element_tree = defusedxml.ElementTree.fromstring(xml_string) + return _extension_element_from_element_tree(element_tree) + + +--- python-pysaml2-3.0.0.orig/src/saml2/pack.py ++++ python-pysaml2-3.0.0/src/saml2/pack.py +@@ -35,6 +35,7 @@ except ImportError: + import cElementTree as ElementTree + except ImportError: + from elementtree import ElementTree ++import defusedxml.ElementTree + + NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/" + FORM_SPEC = """
+@@ -211,7 +212,7 @@ def parse_soap_enveloped_saml(text, body + :param text: The SOAP object as XML + :return: header parts and body as saml.samlbase instances + """ +- envelope = ElementTree.fromstring(text) ++ envelope = defusedxml.ElementTree.fromstring(text) + assert envelope.tag == '{%s}Envelope' % NAMESPACE + + #print(len(envelope)) +--- python-pysaml2-3.0.0.orig/src/saml2/soap.py ++++ python-pysaml2-3.0.0/src/saml2/soap.py +@@ -19,6 +19,7 @@ except ImportError: + except ImportError: + #noinspection PyUnresolvedReferences + from elementtree import ElementTree ++import defusedxml.ElementTree + + + logger = logging.getLogger(__name__) +@@ -133,7 +134,7 @@ def parse_soap_enveloped_saml_thingy(tex + :param expected_tags: What the tag of the SAML thingy is expected to be. + :return: SAML thingy as a string + """ +- envelope = ElementTree.fromstring(text) ++ envelope = defusedxml.ElementTree.fromstring(text) + + # Make sure it's a SOAP message + assert envelope.tag == '{%s}Envelope' % soapenv.NAMESPACE +@@ -183,7 +184,7 @@ def class_instances_from_soap_enveloped_ + :return: The body and headers as class instances + """ + try: +- envelope = ElementTree.fromstring(text) ++ envelope = defusedxml.ElementTree.fromstring(text) + except Exception as exc: + raise XmlParseError("%s" % exc) + +@@ -209,7 +210,7 @@ def open_soap_envelope(text): + :return: dictionary with two keys "body"/"header" + """ + try: +- envelope = ElementTree.fromstring(text) ++ envelope = defusedxml.ElementTree.fromstring(text) + except Exception as exc: + raise XmlParseError("%s" % exc) + +--- python-pysaml2-3.0.0.orig/tests/test_03_saml2.py ++++ python-pysaml2-3.0.0/tests/test_03_saml2.py +@@ -17,6 +17,7 @@ except ImportError: + import cElementTree as ElementTree + except ImportError: + from elementtree import ElementTree ++from defusedxml.common import EntitiesForbidden + + ITEMS = { + NameID: [""" +@@ -166,6 +167,19 @@ def test_create_class_from_xml_string_wr + assert kl == None + + ++def test_create_class_from_xml_string_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(EntitiesForbidden) as err: ++ create_class_from_xml_string(NameID, xml) ++ ++ + def test_ee_1(): + ee = saml2.extension_element_from_string( + """bar""") +@@ -454,6 +468,19 @@ def test_ee_7(): + assert nid.text.strip() == "http://federationX.org" + + ++def test_ee_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(EntitiesForbidden): ++ saml2.extension_element_from_string(xml) ++ ++ + def test_extension_element_loadd(): + ava = {'attributes': {}, + 'tag': 'ExternalEntityAttributeAuthority', +--- python-pysaml2-3.0.0.orig/tests/test_43_soap.py ++++ python-pysaml2-3.0.0/tests/test_43_soap.py +@@ -12,9 +12,13 @@ except ImportError: + import cElementTree as ElementTree + except ImportError: + from elementtree import ElementTree ++from defusedxml.common import EntitiesForbidden ++ ++from pytest import raises + + import saml2.samlp as samlp + from saml2.samlp import NAMESPACE as SAMLP_NAMESPACE ++from saml2 import soap + + NAMESPACE = "http://schemas.xmlsoap.org/soap/envelope/" + +@@ -66,3 +70,42 @@ def test_make_soap_envelope(): + assert len(body) == 1 + saml_part = body[0] + assert saml_part.tag == '{%s}AuthnRequest' % SAMLP_NAMESPACE ++ ++ ++def test_parse_soap_enveloped_saml_thingy_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(EntitiesForbidden): ++ soap.parse_soap_enveloped_saml_thingy(xml, None) ++ ++ ++def test_class_instances_from_soap_enveloped_saml_thingies_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(soap.XmlParseError): ++ soap.class_instances_from_soap_enveloped_saml_thingies(xml, None) ++ ++ ++def test_open_soap_envelope_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(soap.XmlParseError): ++ soap.open_soap_envelope(xml) +--- python-pysaml2-3.0.0.orig/tests/test_51_client.py ++++ python-pysaml2-3.0.0/tests/test_51_client.py +@@ -4,6 +4,8 @@ + import base64 + import uuid + import six ++from pytest import raises ++ + from six.moves.urllib.parse import parse_qs, urlencode, urlparse + from saml2.cert import OpenSSLWrapper + from saml2.xmldsig import SIG_RSA_SHA256 +@@ -21,6 +23,7 @@ from saml2.assertion import Assertion + from saml2.authn_context import INTERNETPROTOCOLPASSWORD + from saml2.client import Saml2Client + from saml2.config import SPConfig ++from saml2.pack import parse_soap_enveloped_saml + from saml2.response import LogoutResponse + from saml2.saml import NAMEID_FORMAT_PERSISTENT, EncryptedAssertion, Advice + from saml2.saml import NAMEID_FORMAT_TRANSIENT +@@ -33,6 +36,8 @@ from saml2.s_utils import do_attribute_s + from saml2.s_utils import factory + from saml2.time_util import in_a_while + ++from defusedxml.common import EntitiesForbidden ++ + from fakeIDP import FakeIDP + from fakeIDP import unpack_form + from pathutils import full_path +@@ -1331,6 +1336,17 @@ class TestClientWithDummy(): + 'http://www.example.com/login' + assert ac.authn_context_class_ref.text == INTERNETPROTOCOLPASSWORD + ++def test_parse_soap_enveloped_saml_xxe(): ++ xml = """ ++ ++ ++ ++ ]> ++ &lol1; ++ """ ++ with raises(EntitiesForbidden): ++ parse_soap_enveloped_saml(xml, None) + + # if __name__ == "__main__": + # tc = TestClient() diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 0000000..39874ed --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +fix-xxe-in-xml-parsing.patch