Log message for revision 129553: Defend against minidom-based DoS in webdav. Patch from Christian Heimes. Addresses LP #1114688.
Changed: _U Zope/branches/2.12/ U Zope/branches/2.12/doc/CHANGES.rst U Zope/branches/2.12/src/webdav/tests/test_xmltools.py U Zope/branches/2.12/src/webdav/xmltools.py -=- Modified: Zope/branches/2.12/doc/CHANGES.rst =================================================================== --- Zope/branches/2.12/doc/CHANGES.rst 2013-02-20 23:37:08 UTC (rev 129552) +++ Zope/branches/2.12/doc/CHANGES.rst 2013-02-20 23:37:08 UTC (rev 129553) @@ -8,6 +8,9 @@ 2.12.27 (unreleased) -------------------- +- LP #1114688: Defend against minidom-based DoS in webdav. (Patch from + Christian Heimes). + - LP #978980: Protect views of ZPT source with 'View Management Screens' permision. Modified: Zope/branches/2.12/src/webdav/tests/test_xmltools.py =================================================================== --- Zope/branches/2.12/src/webdav/tests/test_xmltools.py 2013-02-20 23:37:08 UTC (rev 129552) +++ Zope/branches/2.12/src/webdav/tests/test_xmltools.py 2013-02-20 23:37:08 UTC (rev 129553) @@ -1,19 +1,16 @@ import unittest -class TestNode(unittest.TestCase): +class NodeTests(unittest.TestCase): def _getTargetClass(self): from webdav.xmltools import Node return Node def _makeOne(self, wrapped): - klass = self._getTargetClass() - return klass(wrapped) + return self._getTargetClass()(wrapped) def test_remove_namespace_attrs(self): - """ A method added in Zope 2.11 which removes any attributes - which appear to be XML namespace declarations """ - class DummyMinidomNode: + class DummyMinidomNode(object): def __init__(self): self.attributes = {'xmlns:foo':'foo', 'xmlns':'bar', 'a':'b'} def hasAttributes(self): @@ -27,10 +24,36 @@ self.assertEqual(wrapped.attributes, {'a':'b'}) +class XmlParserTests(unittest.TestCase): + + def _getTargetClass(self): + from webdav.xmltools import XmlParser + return XmlParser + + def _makeOne(self): + return self._getTargetClass()() + + def test_parse_rejects_entities(self): + XML = '\n'.join([ + '<!DOCTYPE dt_test [', + '<!ENTITY entity "1234567890" >', + ']>', + '<test>&entity;</test>' + ]) + parser = self._makeOne() + self.assertRaises(ValueError, parser.parse, XML) + + def test_parse_rejects_doctype_wo_entities(self): + XML = '\n'.join([ + '<!DOCTYPE dt_test []>', + '<test/>' + ]) + parser = self._makeOne() + self.assertRaises(ValueError, parser.parse, XML) + + def test_suite(): return unittest.TestSuite(( - unittest.makeSuite(TestNode), - )) - -if __name__ == '__main__': - unittest.main(defaultTest='test_suite') + unittest.makeSuite(NodeTests), + unittest.makeSuite(XmlParserTests), + )) Modified: Zope/branches/2.12/src/webdav/xmltools.py =================================================================== --- Zope/branches/2.12/src/webdav/xmltools.py 2013-02-20 23:37:08 UTC (rev 129552) +++ Zope/branches/2.12/src/webdav/xmltools.py 2013-02-20 23:37:08 UTC (rev 129553) @@ -36,7 +36,9 @@ from StringIO import StringIO from xml.dom import minidom -from xml.sax.saxutils import escape as _escape, unescape as _unescape +from xml.sax.expatreader import ExpatParser +from xml.sax.saxutils import escape as _escape +from xml.sax.saxutils import unescape as _unescape escape_entities = {'"': '"', "'": ''', @@ -171,6 +173,36 @@ writer.write(value) return writer.getvalue() + +class ProtectedExpatParser(ExpatParser): + """ See https://bugs.launchpad.net/zope2/+bug/1114688 + """ + def __init__(self, forbid_dtd=True, forbid_entities=True, + *args, **kwargs): + # Python 2.x old style class + ExpatParser.__init__(self, *args, **kwargs) + self.forbid_dtd = forbid_dtd + self.forbid_entities = forbid_entities + + def start_doctype_decl(self, name, sysid, pubid, has_internal_subset): + raise ValueError("Inline DTD forbidden") + + def entity_decl(self, entityName, is_parameter_entity, value, base, systemId, publicId, notationName): + raise ValueError("<!ENTITY> forbidden") + + def unparsed_entity_decl(self, name, base, sysid, pubid, notation_name): + # expat 1.2 + raise ValueError("<!ENTITY> forbidden") + + def reset(self): + ExpatParser.reset(self) + if self.forbid_dtd: + self._parser.StartDoctypeDeclHandler = self.start_doctype_decl + if self.forbid_entities: + self._parser.EntityDeclHandler = self.entity_decl + self._parser.UnparsedEntityDeclHandler = self.unparsed_entity_decl + + class XmlParser: """ Simple wrapper around minidom to support the required interfaces for zope.webdav @@ -182,5 +214,5 @@ pass def parse(self, data): - self.dom = minidom.parseString(data) + self.dom = minidom.parseString(data, parser=ProtectedExpatParser()) return Node(self.dom) _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org https://mail.zope.org/mailman/listinfo/zope-checkins