Commit 722547c7 authored by Tres Seaver's avatar Tres Seaver

Defend against minidom-based DoS in webdav.

Patch from Christian Heimes.

Addresses LP #1114688.
parent 21da65a2
...@@ -8,6 +8,9 @@ http://docs.zope.org/zope2/releases/. ...@@ -8,6 +8,9 @@ http://docs.zope.org/zope2/releases/.
2.13.20 (unreleased) 2.13.20 (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' - LP #978980: Protect views of ZPT source with 'View Management Screens'
permision. permision.
......
import unittest import unittest
class TestNode(unittest.TestCase): class NodeTests(unittest.TestCase):
def _getTargetClass(self): def _getTargetClass(self):
from webdav.xmltools import Node from webdav.xmltools import Node
return Node return Node
def _makeOne(self, wrapped): def _makeOne(self, wrapped):
klass = self._getTargetClass() return self._getTargetClass()(wrapped)
return klass(wrapped)
def test_remove_namespace_attrs(self): def test_remove_namespace_attrs(self):
""" A method added in Zope 2.11 which removes any attributes class DummyMinidomNode(object):
which appear to be XML namespace declarations """
class DummyMinidomNode:
def __init__(self): def __init__(self):
self.attributes = {'xmlns:foo':'foo', 'xmlns':'bar', 'a':'b'} self.attributes = {'xmlns:foo':'foo', 'xmlns':'bar', 'a':'b'}
def hasAttributes(self): def hasAttributes(self):
...@@ -27,7 +24,36 @@ class TestNode(unittest.TestCase): ...@@ -27,7 +24,36 @@ class TestNode(unittest.TestCase):
self.assertEqual(wrapped.attributes, {'a':'b'}) 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(): def test_suite():
return unittest.TestSuite(( return unittest.TestSuite((
unittest.makeSuite(TestNode), unittest.makeSuite(NodeTests),
unittest.makeSuite(XmlParserTests),
)) ))
...@@ -35,7 +35,9 @@ TODO: ...@@ -35,7 +35,9 @@ TODO:
from StringIO import StringIO from StringIO import StringIO
from xml.dom import minidom 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;', escape_entities = {'"': '&quot;',
"'": '&apos;', "'": '&apos;',
...@@ -170,6 +172,36 @@ class Element(Node): ...@@ -170,6 +172,36 @@ class Element(Node):
writer.write(value) writer.write(value)
return writer.getvalue() 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: class XmlParser:
""" Simple wrapper around minidom to support the required """ Simple wrapper around minidom to support the required
interfaces for zope.webdav interfaces for zope.webdav
...@@ -181,5 +213,5 @@ class XmlParser: ...@@ -181,5 +213,5 @@ class XmlParser:
pass pass
def parse(self, data): def parse(self, data):
self.dom = minidom.parseString(data) self.dom = minidom.parseString(data, parser=ProtectedExpatParser())
return Node(self.dom) return Node(self.dom)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment