Log message for revision 129555:
  Defend against minidom-based DoS in webdav.
  
  Patch from Christian Heimes.
  
  Addresses LP #1114688.
  

Changed:
  _U  Zope/trunk/
  U   Zope/trunk/doc/CHANGES.rst
  U   Zope/trunk/src/webdav/tests/test_xmltools.py
  U   Zope/trunk/src/webdav/xmltools.py

-=-
Modified: Zope/trunk/doc/CHANGES.rst
===================================================================
--- Zope/trunk/doc/CHANGES.rst  2013-02-20 23:41:00 UTC (rev 129554)
+++ Zope/trunk/doc/CHANGES.rst  2013-02-20 23:43:40 UTC (rev 129555)
@@ -11,6 +11,9 @@
 Bugs Fixed
 ++++++++++
 
+- 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/trunk/src/webdav/tests/test_xmltools.py
===================================================================
--- Zope/trunk/src/webdav/tests/test_xmltools.py        2013-02-20 23:41:00 UTC 
(rev 129554)
+++ Zope/trunk/src/webdav/tests/test_xmltools.py        2013-02-20 23:43:40 UTC 
(rev 129555)
@@ -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,7 +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),
-        ))
+        unittest.makeSuite(NodeTests),
+        unittest.makeSuite(XmlParserTests),
+    ))

Modified: Zope/trunk/src/webdav/xmltools.py
===================================================================
--- Zope/trunk/src/webdav/xmltools.py   2013-02-20 23:41:00 UTC (rev 129554)
+++ Zope/trunk/src/webdav/xmltools.py   2013-02-20 23:43:40 UTC (rev 129555)
@@ -35,7 +35,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 = {'"': '&quot;',
                    "'": '&apos;',
@@ -170,6 +172,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
@@ -181,5 +213,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

Reply via email to