Skip to content

Commit

Permalink
Make XMLRPC server safe from XML attacks
Browse files Browse the repository at this point in the history
- No entity expansions
- No gzip bombs
- No entity references
  • Loading branch information
davidfischer committed May 16, 2014
1 parent d55dbc3 commit ec99b53
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 9 deletions.
14 changes: 11 additions & 3 deletions rpc4django/rpcdispatcher.py
Expand Up @@ -14,10 +14,18 @@

try:
# Python2.x
from xmlrpclib import Fault, loads, ServerProxy
from xmlrpclib import Fault, ServerProxy
except ImportError:
# Python3
from xmlrpc.client import Fault, loads, ServerProxy
from xmlrpc.client import Fault, ServerProxy

from defusedxml import xmlrpc


# This method makes the XMLRPC parser (used by loads) safe
# from various XML based attacks
xmlrpc.monkey_patch()


# this error code is taken from xmlrpc-epi
# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
Expand Down Expand Up @@ -391,7 +399,7 @@ def get_method_name(self, raw_post_data, request_format='xml'):
# xmlrpclib.loads could throw an exception, but this is fine
# since _marshaled_dispatch would throw the same thing
try:
params, method = loads(raw_post_data.decode('utf-8'))
params, method = xmlrpc.xmlrpc_client.loads(raw_post_data.decode('utf-8'))
return method
except Exception:
return None
Expand Down
12 changes: 9 additions & 3 deletions rpc4django/xmlrpcdispatcher.py
Expand Up @@ -6,13 +6,19 @@

try:
# Python2
from xmlrpclib import Fault, loads, dumps
from xmlrpclib import Fault, dumps
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
except ImportError:
# Python3
from xmlrpc.client import Fault, loads, dumps
from xmlrpc.client import Fault, dumps
from xmlrpc.server import SimpleXMLRPCDispatcher

from defusedxml import xmlrpc

# This method makes the XMLRPC parser (used by loads) safe
# from various XML based attacks
xmlrpc.monkey_patch()


class XMLRPCDispatcher(SimpleXMLRPCDispatcher):
"""
Expand Down Expand Up @@ -41,7 +47,7 @@ def dispatch(self, data, **kwargs):
"""

try:
params, method = loads(data)
params, method = xmlrpc.xmlrpc_client.loads(data)

try:
response = self._dispatch(method, params, **kwargs)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -52,7 +52,7 @@
'Topic :: Software Development :: Libraries :: Python Modules',
],

install_requires=['Django >= 1.3'],
install_requires=['Django >= 1.3', 'defusedxml'],
tests_require=['Django >= 1.3'],
extras_require={
"reST": ['docutils >= 0.4'],
Expand Down
24 changes: 22 additions & 2 deletions tests/test_xmlrpcdispatcher.py
Expand Up @@ -17,9 +17,9 @@
from rpc4django.xmlrpcdispatcher import XMLRPCDispatcher

try:
from xmlrpclib import loads, dumps
from xmlrpclib import loads, dumps, Fault
except ImportError:
from xmlrpc.client import loads, dumps
from xmlrpc.client import loads, dumps, Fault


class TestXMLRPCDispatcher(unittest.TestCase):
Expand All @@ -43,6 +43,26 @@ def test_kwargs(self):
out, name = loads(ret)
self.assertTrue(out[0])

def test_billion_laughs(self):
payload = """<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ELEMENT lolz (#PCDATA)>
<!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>"""

ret = self.dispatcher.dispatch(payload)
self.assertRaises(Fault, loads, ret)


if __name__ == '__main__':
unittest.main()

0 comments on commit ec99b53

Please sign in to comment.