XML External Entity attack fix

* Add upstream patch for XML External Entity attack (Closes: #850716).

Change-Id: I2713c4c1489aae2fe39b890d49e3cebaef3ea7ca
This commit is contained in:
Thomas Goirand
2017-01-09 16:50:55 +01:00
parent 1916844e81
commit 381018227c
4 changed files with 272 additions and 4 deletions

8
debian/changelog vendored
View File

@@ -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 * Bumped debhelper compat version to 10
-- Ondřej Nový <onovy@debian.org> Thu, 24 Nov 2016 00:10:41 +0100 [ Thomas Goirand ]
* Add upstream patch for XML External Entity attack (Closes: #850716).
-- Thomas Goirand <zigo@debian.org> Mon, 09 Jan 2017 16:28:55 +0100
python-pysaml2 (3.0.0-4) unstable; urgency=medium python-pysaml2 (3.0.0-4) unstable; urgency=medium

8
debian/control vendored
View File

@@ -14,6 +14,7 @@ Build-Depends-Indep: python-alabaster,
python-crypto, python-crypto,
python-dateutil, python-dateutil,
python-decorator, python-decorator,
python-defusedxml,
python-mako, python-mako,
python-memcache, python-memcache,
python-openssl, python-openssl,
@@ -28,6 +29,7 @@ Build-Depends-Indep: python-alabaster,
python3-crypto, python3-crypto,
python3-dateutil, python3-dateutil,
python3-decorator, python3-decorator,
python3-defusedxml,
python3-mako, python3-mako,
python3-memcache, python3-memcache,
python3-openssl, python3-openssl,
@@ -47,7 +49,8 @@ Homepage: https://github.com/rohe/pysaml2
Package: python-pysaml2 Package: python-pysaml2
Architecture: all Architecture: all
Depends: python-mako, Depends: python-defusedxml,
python-mako,
python-memcache, python-memcache,
python-pyasn1 (>=0.1.8), python-pyasn1 (>=0.1.8),
python-repoze.who, python-repoze.who,
@@ -72,7 +75,8 @@ Description: SAML Version 2 to be used in a WSGI environment - Python 2.x
Package: python3-pysaml2 Package: python3-pysaml2
Architecture: all Architecture: all
Depends: python3-mako, Depends: python3-defusedxml,
python3-mako,
python3-memcache, python3-memcache,
python3-pyasn1 (>=0.1.8), python3-pyasn1 (>=0.1.8),
python3-repoze.who, python3-repoze.who,

View File

@@ -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 <fruechel@atlassian.com>
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 = """<form method="post" action="%s">
@@ -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: ["""<?xml version="1.0" encoding="utf-8"?>
@@ -166,6 +167,19 @@ def test_create_class_from_xml_string_wr
assert kl == None
+def test_create_class_from_xml_string_xxe():
+ xml = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ with raises(EntitiesForbidden) as err:
+ create_class_from_xml_string(NameID, xml)
+
+
def test_ee_1():
ee = saml2.extension_element_from_string(
"""<?xml version='1.0' encoding='UTF-8'?><foo>bar</foo>""")
@@ -454,6 +468,19 @@ def test_ee_7():
assert nid.text.strip() == "http://federationX.org"
+def test_ee_xxe():
+ xml = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ 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 = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ with raises(EntitiesForbidden):
+ soap.parse_soap_enveloped_saml_thingy(xml, None)
+
+
+def test_class_instances_from_soap_enveloped_saml_thingies_xxe():
+ xml = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ with raises(soap.XmlParseError):
+ soap.class_instances_from_soap_enveloped_saml_thingies(xml, None)
+
+
+def test_open_soap_envelope_xxe():
+ xml = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ 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 = """<?xml version="1.0"?>
+ <!DOCTYPE lolz [
+ <!ENTITY lol "lol">
+ <!ELEMENT lolz (#PCDATA)>
+ <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
+ ]>
+ <lolz>&lol1;</lolz>
+ """
+ with raises(EntitiesForbidden):
+ parse_soap_enveloped_saml(xml, None)
# if __name__ == "__main__":
# tc = TestClient()

1
debian/patches/series vendored Normal file
View File

@@ -0,0 +1 @@
fix-xxe-in-xml-parsing.patch