Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
  • Loading branch information
fruechel committed Oct 31, 2016
commit 6e09a25d9b4b7aa7a506853210a9a14100b8bc9b
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
'pytz',
'pyOpenSSL',
'python-dateutil',
'defusedxml',
'six'
]

Expand Down
5 changes: 3 additions & 2 deletions src/saml2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import cElementTree as ElementTree
except ImportError:
from elementtree import ElementTree
import defusedxml.ElementTree

root_logger = logging.getLogger(__name__)
root_logger.level = logging.NOTSET
Expand Down Expand Up @@ -87,7 +88,7 @@ def create_class_from_xml_string(target_class, xml_string):
"""
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)


Expand Down Expand Up @@ -269,7 +270,7 @@ def loadd(self, ava):


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)


Expand Down
3 changes: 2 additions & 1 deletion src/saml2/pack.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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">
Expand Down Expand Up @@ -235,7 +236,7 @@ def parse_soap_enveloped_saml(text, body_class, header_class=None):
: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))
Expand Down
7 changes: 4 additions & 3 deletions src/saml2/soap.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
except ImportError:
#noinspection PyUnresolvedReferences
from elementtree import ElementTree
import defusedxml.ElementTree


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -133,7 +134,7 @@ def parse_soap_enveloped_saml_thingy(text, expected_tags):
: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
Expand Down Expand Up @@ -183,7 +184,7 @@ def class_instances_from_soap_enveloped_saml_thingies(text, modules):
: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)

Expand All @@ -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)

Expand Down
27 changes: 27 additions & 0 deletions tests/test_03_saml2.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import cElementTree as ElementTree
except ImportError:
from elementtree import ElementTree
from defusedxml.common import EntitiesForbidden

ITEMS = {
NameID: ["""<?xml version="1.0" encoding="utf-8"?>
Expand Down Expand Up @@ -166,6 +167,19 @@ def test_create_class_from_xml_string_wrong_class_spec():
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>""")
Expand Down Expand Up @@ -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',
Expand Down
43 changes: 43 additions & 0 deletions tests/test_43_soap.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@
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/"

Expand Down Expand Up @@ -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)
15 changes: 15 additions & 0 deletions tests/test_51_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from future.backports.urllib.parse import parse_qs
from future.backports.urllib.parse import urlencode
from future.backports.urllib.parse import urlparse
from pytest import raises

from saml2.argtree import add_path
from saml2.cert import OpenSSLWrapper
Expand All @@ -25,6 +26,7 @@
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
Expand All @@ -38,6 +40,8 @@
from saml2.s_utils import factory
from saml2.time_util import in_a_while, a_while_ago

from defusedxml.common import EntitiesForbidden

from fakeIDP import FakeIDP
from fakeIDP import unpack_form
from pathutils import full_path
Expand Down Expand Up @@ -1552,6 +1556,17 @@ def test_negotiated_post_sso(self):
'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()
Expand Down