Moderate severityNVD Advisory· Published Apr 3, 2013· Updated Apr 29, 2026
CVE-2013-1665
CVE-2013-1665
Description
The XML libraries for Python 3.4, 3.3, 3.2, 3.1, 2.7, and 2.6, as used in OpenStack Keystone Essex and Folsom, Django, and possibly other products allow remote attackers to read arbitrary files via an XML external entity declaration in conjunction with an entity reference, aka an XML External Entity (XXE) attack.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
DjangoPyPI | >= 1.3.0, < 1.3.6 | 1.3.6 |
DjangoPyPI | >= 1.4.0, < 1.4.4 | 1.4.4 |
Affected products
2- cpe:2.3:a:openstack:keystone_essex:-:*:*:*:*:*:*:*
Patches
2d19a27066b22[1.3.x] Restrict the XML deserializer to prevent network and entity-expansion DoS attacks.
2 files changed · +108 −1
django/core/serializers/xml_serializer.py+93 −1 modified@@ -8,6 +8,8 @@ from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import smart_unicode from xml.dom import pulldom +from xml.sax import handler +from xml.sax.expatreader import ExpatParser as _ExpatParser class Serializer(base.Serializer): """ @@ -154,9 +156,13 @@ class Deserializer(base.Deserializer): def __init__(self, stream_or_string, **options): super(Deserializer, self).__init__(stream_or_string, **options) - self.event_stream = pulldom.parse(self.stream) + self.event_stream = pulldom.parse(self.stream, self._make_parser()) self.db = options.pop('using', DEFAULT_DB_ALIAS) + def _make_parser(self): + """Create a hardened XML parser (no custom/external entities).""" + return DefusedExpatParser() + def next(self): for event, node in self.event_stream: if event == "START_ELEMENT" and node.nodeName == "object": @@ -295,3 +301,89 @@ def getInnerText(node): else: pass return u"".join(inner_text) + + +# Below code based on Christian Heimes' defusedxml + + +class DefusedExpatParser(_ExpatParser): + """ + An expat parser hardened against XML bomb attacks. + + Forbids DTDs, external entity references + + """ + def __init__(self, *args, **kwargs): + _ExpatParser.__init__(self, *args, **kwargs) + self.setFeature(handler.feature_external_ges, False) + self.setFeature(handler.feature_external_pes, False) + + def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def reset(self): + _ExpatParser.reset(self) + parser = self._parser + parser.StartDoctypeDeclHandler = self.start_doctype_decl + parser.EntityDeclHandler = self.entity_decl + parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl + parser.ExternalEntityRefHandler = self.external_entity_ref_handler + + +class DefusedXmlException(ValueError): + """Base exception.""" + def __repr__(self): + return str(self) + + +class DTDForbidden(DefusedXmlException): + """Document type definition is forbidden.""" + def __init__(self, name, sysid, pubid): + self.name = name + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class EntitiesForbidden(DefusedXmlException): + """Entity definition is forbidden.""" + def __init__(self, name, value, base, sysid, pubid, notation_name): + super(EntitiesForbidden, self).__init__() + self.name = name + self.value = value + self.base = base + self.sysid = sysid + self.pubid = pubid + self.notation_name = notation_name + + def __str__(self): + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class ExternalReferenceForbidden(DefusedXmlException): + """Resolving an external reference is forbidden.""" + def __init__(self, context, base, sysid, pubid): + super(ExternalReferenceForbidden, self).__init__() + self.context = context + self.base = base + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" + return tpl.format(self.sysid, self.pubid)
tests/regressiontests/serializers_regress/tests.py+15 −0 modified@@ -14,6 +14,7 @@ from cStringIO import StringIO except ImportError: from StringIO import StringIO +from django.core.serializers.xml_serializer import DTDForbidden from django.conf import settings from django.core import serializers, management @@ -416,3 +417,17 @@ def streamTest(format, self): setattr(SerializerTests, 'test_' + format + '_serializer_fields', curry(fieldsTest, format)) if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) + + +class XmlDeserializerSecurityTests(TestCase): + + def test_no_dtd(self): + """ + The XML deserializer shouldn't allow a DTD. + + This is the most straightforward way to prevent all entity definitions + and avoid both external entities and entity-expansion attacks. + + """ + xml = '<?xml version="1.0" standalone="no"?><!DOCTYPE example SYSTEM "http://example.com/example.dtd">' + self.assertRaises(DTDForbidden, serializers.deserialize('xml', xml).next)
1c60d07ba23e[1.4.x] Restrict the XML deserializer to prevent network and entity-expansion DoS attacks.
2 files changed · +108 −1
django/core/serializers/xml_serializer.py+94 −1 modified@@ -8,6 +8,8 @@ from django.utils.xmlutils import SimplerXMLGenerator from django.utils.encoding import smart_unicode from xml.dom import pulldom +from xml.sax import handler +from xml.sax.expatreader import ExpatParser as _ExpatParser class Serializer(base.Serializer): """ @@ -149,9 +151,13 @@ class Deserializer(base.Deserializer): def __init__(self, stream_or_string, **options): super(Deserializer, self).__init__(stream_or_string, **options) - self.event_stream = pulldom.parse(self.stream) + self.event_stream = pulldom.parse(self.stream, self._make_parser()) self.db = options.pop('using', DEFAULT_DB_ALIAS) + def _make_parser(self): + """Create a hardened XML parser (no custom/external entities).""" + return DefusedExpatParser() + def next(self): for event, node in self.event_stream: if event == "START_ELEMENT" and node.nodeName == "object": @@ -290,3 +296,90 @@ def getInnerText(node): else: pass return u"".join(inner_text) + + +# Below code based on Christian Heimes' defusedxml + + +class DefusedExpatParser(_ExpatParser): + """ + An expat parser hardened against XML bomb attacks. + + Forbids DTDs, external entity references + + """ + def __init__(self, *args, **kwargs): + _ExpatParser.__init__(self, *args, **kwargs) + self.setFeature(handler.feature_external_ges, False) + self.setFeature(handler.feature_external_pes, False) + + def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise DTDForbidden(name, sysid, pubid) + + def entity_decl(self, name, is_parameter_entity, value, base, + sysid, pubid, notation_name): + raise EntitiesForbidden(name, value, base, sysid, pubid, notation_name) + + def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise EntitiesForbidden(name, None, base, sysid, pubid, notation_name) + + def external_entity_ref_handler(self, context, base, sysid, pubid): + raise ExternalReferenceForbidden(context, base, sysid, pubid) + + def reset(self): + _ExpatParser.reset(self) + parser = self._parser + parser.StartDoctypeDeclHandler = self.start_doctype_decl + parser.EntityDeclHandler = self.entity_decl + parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl + parser.ExternalEntityRefHandler = self.external_entity_ref_handler + + +class DefusedXmlException(ValueError): + """Base exception.""" + def __repr__(self): + return str(self) + + +class DTDForbidden(DefusedXmlException): + """Document type definition is forbidden.""" + def __init__(self, name, sysid, pubid): + super(DTDForbidden, self).__init__() + self.name = name + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "DTDForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class EntitiesForbidden(DefusedXmlException): + """Entity definition is forbidden.""" + def __init__(self, name, value, base, sysid, pubid, notation_name): + super(EntitiesForbidden, self).__init__() + self.name = name + self.value = value + self.base = base + self.sysid = sysid + self.pubid = pubid + self.notation_name = notation_name + + def __str__(self): + tpl = "EntitiesForbidden(name='{}', system_id={!r}, public_id={!r})" + return tpl.format(self.name, self.sysid, self.pubid) + + +class ExternalReferenceForbidden(DefusedXmlException): + """Resolving an external reference is forbidden.""" + def __init__(self, context, base, sysid, pubid): + super(ExternalReferenceForbidden, self).__init__() + self.context = context + self.base = base + self.sysid = sysid + self.pubid = pubid + + def __str__(self): + tpl = "ExternalReferenceForbidden(system_id='{}', public_id={})" + return tpl.format(self.sysid, self.pubid)
tests/regressiontests/serializers_regress/tests.py+14 −0 modified@@ -16,6 +16,7 @@ from cStringIO import StringIO except ImportError: from StringIO import StringIO +from django.core.serializers.xml_serializer import DTDForbidden try: import yaml @@ -523,3 +524,16 @@ def streamTest(format, self): if format != 'python': setattr(SerializerTests, 'test_' + format + '_serializer_stream', curry(streamTest, format)) + +class XmlDeserializerSecurityTests(TestCase): + + def test_no_dtd(self): + """ + The XML deserializer shouldn't allow a DTD. + + This is the most straightforward way to prevent all entity definitions + and avoid both external entities and entity-expansion attacks. + + """ + xml = '<?xml version="1.0" standalone="no"?><!DOCTYPE example SYSTEM "http://example.com/example.dtd">' + self.assertRaises(DTDForbidden, serializers.deserialize('xml', xml).next)
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- bugs.launchpad.net/keystone/+bug/1100279nvdPatchWEB
- lists.openstack.org/pipermail/openstack-announce/2013-February/000078.htmlnvdVendor AdvisoryWEB
- github.com/advisories/GHSA-x64m-686f-fmm3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2013-1665ghsaADVISORY
- blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.htmlnvdWEB
- bugs.python.org/issue17239nvdWEB
- rhn.redhat.com/errata/RHSA-2013-0657.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2013-0658.htmlnvdWEB
- rhn.redhat.com/errata/RHSA-2013-0670.htmlnvdWEB
- ubuntu.com/usn/usn-1757-1nvdWEB
- www.debian.org/security/2013/dsa-2634nvdWEB
- www.openwall.com/lists/oss-security/2013/02/19/2nvdWEB
- www.openwall.com/lists/oss-security/2013/02/19/4nvdWEB
- github.com/django/django/commit/1c60d07ba23e0350351c278ad28d0bd5aa410b40ghsaWEB
- github.com/django/django/commit/d19a27066b2247102e65412aa66917aff0091112ghsaWEB
News mentions
0No linked articles in our index yet.