XML External Entity attack fix
* Add upstream patch for XML External Entity attack (Closes: #850716). Change-Id: I2713c4c1489aae2fe39b890d49e3cebaef3ea7ca
This commit is contained in:
8
debian/changelog
vendored
8
debian/changelog
vendored
@@ -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
8
debian/control
vendored
@@ -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,
|
||||||
|
|||||||
259
debian/patches/fix-xxe-in-xml-parsing.patch
vendored
Normal file
259
debian/patches/fix-xxe-in-xml-parsing.patch
vendored
Normal 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
1
debian/patches/series
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
fix-xxe-in-xml-parsing.patch
|
||||||
Reference in New Issue
Block a user