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
|
||||
|
||||
-- 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
|
||||
|
||||
|
||||
8
debian/control
vendored
8
debian/control
vendored
@@ -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,
|
||||
|
||||
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