1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
|
Description: CVE-2016-10127 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.
.
The patch has been backported form the 3.0 branch to 2.0 by zigo@debian.org.
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-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 = """<form method="post" action="%s">
@@ -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: ["""<?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-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 = """<?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-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 = """<?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()
|