--- file_not_specified_in_diff +++ file_not_specified_in_diff @@ -, +, @@ 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. . The patch has been backported form the 3.0 branch to 2.0 by zigo@debian.org. --- python-pysaml2-2.0.0.orig/setup.py +++ python-pysaml2-2.0.0/setup.py @@ -46,7 +46,8 @@ install_requires = [ 'pycrypto', # 'Crypto' 'pytz', 'pyOpenSSL', - 'python-dateutil' + 'python-dateutil', + 'defusedxml' ] tests_require = [ --- python-pysaml2-2.0.0.orig/src/saml2/__init__.py +++ python-pysaml2-2.0.0/src/saml2/__init__.py @@ -33,6 +33,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 @@ -82,7 +83,7 @@ def create_class_from_xml_string(target_ the contents of the XML - or None if the root XML tag and namespace did not match those of the target class. """ - tree = ElementTree.fromstring(xml_string) + tree = defusedxml.ElementTree.fromstring(xml_string) return create_class_from_element_tree(target_class, tree) @@ -264,7 +265,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-2.0.0.orig/src/saml2/pack.py +++ python-pysaml2-2.0.0/src/saml2/pack.py @@ -48,6 +48,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 = """
@@ -218,7 +219,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-2.0.0.orig/src/saml2/soap.py +++ python-pysaml2-2.0.0/src/saml2/soap.py @@ -32,6 +32,7 @@ except ImportError: except ImportError: #noinspection PyUnresolvedReferences from elementtree import ElementTree +import defusedxml.ElementTree logger = logging.getLogger(__name__) @@ -146,7 +147,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 @@ -196,7 +197,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, exc: raise XmlParseError("%s" % exc) @@ -222,7 +223,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, exc: raise XmlParseError("%s" % exc) --- python-pysaml2-2.0.0.orig/tests/test_03_saml2.py +++ python-pysaml2-2.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-2.0.0.orig/tests/test_43_soap.py +++ python-pysaml2-2.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-2.0.0.orig/tests/test_51_client.py +++ python-pysaml2-2.0.0/tests/test_51_client.py @@ -4,6 +4,7 @@ import base64 import urllib import urlparse + from saml2.authn_context import INTERNETPROTOCOLPASSWORD from saml2.response import LogoutResponse @@ -11,6 +12,7 @@ from saml2.client import Saml2Client from saml2 import samlp, BINDING_HTTP_POST, BINDING_HTTP_REDIRECT from saml2 import saml, config, class_name from saml2.config import SPConfig +from saml2.pack import parse_soap_enveloped_saml from saml2.saml import NAMEID_FORMAT_PERSISTENT from saml2.saml import NAMEID_FORMAT_TRANSIENT from saml2.saml import NameID @@ -18,6 +20,7 @@ from saml2.server import Server from saml2.time_util import in_a_while from py.test import raises +from defusedxml.common import EntitiesForbidden from fakeIDP import FakeIDP, unpack_form @@ -439,6 +442,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()