Philipp Hörist pushed to branch master at gajim / python-nbxmpp
Commits: 70de8004 by Philipp Hörist at 2018-11-12T20:22:14Z Harden parser against xml vulnerabilities - - - - - 5 changed files: - nbxmpp/dispatcher_nb.py - nbxmpp/simplexml.py - + test/__init__.py - test/runtests.py - + test/unit/test_xml_vulnerability.py Changes: ===================================== nbxmpp/dispatcher_nb.py ===================================== @@ -235,8 +235,9 @@ class XMPPDispatcher(PlugIn): if self.Stream and self.Stream.has_received_endtag(): self._owner.disconnect(self.Stream.streamError) return 0 - except ExpatError: + except ExpatError as error: log.error('Invalid XML received from server. Forcing disconnect.') + log.error(error) self._owner.Connection.disconnect() return 0 except ValueError as e: ===================================== nbxmpp/simplexml.py ===================================== @@ -22,6 +22,7 @@ from __future__ import unicode_literals import sys import xml.parsers.expat +from xml.parsers.expat import ExpatError import logging log = logging.getLogger('nbxmpp.simplexml') @@ -566,10 +567,17 @@ class NodeBuilder(object): """ log.debug("Preparing to handle incoming XML stream.") self._parser = xml.parsers.expat.ParserCreate() + self._parser.UseForeignDTD(False) self._parser.StartElementHandler = self.starttag self._parser.EndElementHandler = self.endtag self._parser.StartNamespaceDeclHandler = self.handle_namespace_start self._parser.CharacterDataHandler = self.handle_cdata + self._parser.StartDoctypeDeclHandler = self.handle_invalid_xmpp_element + self._parser.EntityDeclHandler = self.handle_invalid_xmpp_element + self._parser.CommentHandler = self.handle_invalid_xmpp_element + self._parser.ExternalEntityRefHandler = self.handle_invalid_xmpp_element + self._parser.AttlistDeclHandler = self.handle_invalid_xmpp_element + self._parser.ProcessingInstructionHandler = self.handle_invalid_xmpp_element self._parser.buffer_text = True self.Parse = self._parser.Parse @@ -673,6 +681,10 @@ class NodeBuilder(object): self.data_buffer = [data] self.last_is_data = 1 + @staticmethod + def handle_invalid_xmpp_element(*args): + raise ExpatError('Found invalid xmpp stream element: %s' % str(args)) + def handle_namespace_start(self, prefix, uri): """ XML Parser callback. Used internally ===================================== test/__init__.py ===================================== ===================================== test/runtests.py ===================================== @@ -38,6 +38,7 @@ modules = ( 'unit.test_xmpp_dispatcher_nb', 'unit.test_xmpp_smacks', #'unit.test_xmpp_client_nb', gajim.org only supports TLS/SSL connections 'unit.test_xmpp_transports_nb2', + 'unit.test_xml_vulnerability', ) nb_errors = 0 ===================================== test/unit/test_xml_vulnerability.py ===================================== @@ -0,0 +1,73 @@ +import unittest +from unittest.mock import Mock + +from nbxmpp import dispatcher_nb + + +class XMLVulnerability(unittest.TestCase): + + def setUp(self): + self.client = Mock() + self.client.Connection = Mock() + self.dispatcher = dispatcher_nb.XMPPDispatcher() + self.dispatcher.PlugIn(self.client) + self.dispatcher.StreamInit() + + def tearDown(self): + self.dispatcher = None + self.client = None + + def test_exponential_entity_expansion(self): + bomb = """<?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE bomb [ + <!ENTITY a "test"> + <!ENTITY b "&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;"> + <!ENTITY c "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + ]> + <bomb>&c;</bomb>""" + + self.dispatcher.ProcessNonBlocking(bomb) + self.client.Connection.disconnect.assert_called() + + def test_quadratic_blowup(self): + bomb = """<?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE bomb [ + <!ENTITY a "xxxxxxx... a couple of ten thousand chars"> + ]> + <bomb>&a;&a;&a;... repeat</bomb>""" + + self.dispatcher.ProcessNonBlocking(bomb) + self.client.Connection.disconnect.assert_called() + + def test_external_entity_expansion(self): + bomb = """<?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE external [ + <!ENTITY ee SYSTEM "http://www.python.org/some.xml"> + ]> + <root>ⅇ</root>""" + + self.dispatcher.ProcessNonBlocking(bomb) + self.client.Connection.disconnect.assert_called() + + def test_external_local_entity_expansion(self): + bomb = """<?xml version="1.0" encoding="utf-8"?> + <stream:stream xmlns:stream='http://etherx.jabber.org/streams' xmlns='jabber:client'> + <!DOCTYPE external [ + <!ENTITY ee SYSTEM "file:///PATH/TO/simple.xml"> + ]> + <root>ⅇ</root>""" + + self.dispatcher.ProcessNonBlocking(bomb) + self.client.Connection.disconnect.assert_called() + + def test_dtd_retrival(self): + bomb = """<?xml version="1.0" encoding="utf-8"?> + <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + <html> + <head/> + <body>text</body> + </html>""" + + self.dispatcher.ProcessNonBlocking(bomb) + self.client.Connection.disconnect.assert_called() View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/commit/70de8004a84ef62e0e2c25bc1c882e92a9a89e94 -- View it on GitLab: https://dev.gajim.org/gajim/python-nbxmpp/commit/70de8004a84ef62e0e2c25bc1c882e92a9a89e94 You're receiving this email because of your account on dev.gajim.org.
_______________________________________________ Commits mailing list Commits@gajim.org https://lists.gajim.org/cgi-bin/listinfo/commits