Skip to content

Commit aac3c21

Browse files
committed
Fix CVE-2017-11610 by disabling object traversal in XML-RPC dispatch
1 parent 8286f01 commit aac3c21

File tree

2 files changed

+94
-24
lines changed

2 files changed

+94
-24
lines changed

supervisor/tests/test_xmlrpc.py

+75-14
Original file line numberDiff line numberDiff line change
@@ -269,28 +269,89 @@ def test_continue_request_500_if_xmlrpc_dumps_raises(self):
269269
self.assertEqual(request._error, 500)
270270

271271
class TraverseTests(unittest.TestCase):
272-
def test_underscore(self):
272+
def test_security_disallows_underscore_methods(self):
273273
from supervisor import xmlrpc
274-
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse, None, '_', None)
274+
class Root:
275+
pass
276+
class A:
277+
def _danger(self):
278+
return True
279+
root = Root()
280+
root.a = A()
281+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
282+
root, 'a._danger', [])
283+
284+
def test_security_disallows_object_traversal(self):
285+
from supervisor import xmlrpc
286+
class Root:
287+
pass
288+
class A:
289+
pass
290+
class B:
291+
def danger(self):
292+
return True
293+
root = Root()
294+
root.a = A()
295+
root.a.b = B()
296+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
297+
root, 'a.b.danger', [])
298+
299+
def test_namespace_name_not_found(self):
300+
from supervisor import xmlrpc
301+
class Root:
302+
pass
303+
root = Root()
304+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
305+
root, 'notfound.hello', None)
275306

276-
def test_notfound(self):
307+
def test_method_name_not_found(self):
277308
from supervisor import xmlrpc
278-
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse, None, 'foo', None)
309+
class Root:
310+
pass
311+
class A:
312+
pass
313+
root = Root()
314+
root.a = A()
315+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
316+
root, 'a.notfound', [])
279317

280-
def test_badparams(self):
318+
def test_method_name_exists_but_is_not_a_method(self):
281319
from supervisor import xmlrpc
282-
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse, self,
283-
'test_badparams', (1, 2, 3))
320+
class Root:
321+
pass
322+
class A:
323+
pass
324+
class B:
325+
pass
326+
root = Root()
327+
root.a = A()
328+
root.a.b = B()
329+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
330+
root, 'a.b', []) # b is not a method
331+
332+
def test_bad_params(self):
333+
from supervisor import xmlrpc
334+
class Root:
335+
pass
336+
class A:
337+
def hello(self, name):
338+
return "Hello %s" % name
339+
root = Root()
340+
root.a = A()
341+
self.assertRaises(xmlrpc.RPCError, xmlrpc.traverse,
342+
root, 'a.hello', ["there", "extra"]) # too many params
284343

285344
def test_success(self):
286345
from supervisor import xmlrpc
287-
L = []
288-
class Dummy:
289-
def foo(self, a):
290-
L.append(a)
291-
dummy = Dummy()
292-
xmlrpc.traverse(dummy, 'foo', [1])
293-
self.assertEqual(L, [1])
346+
class Root:
347+
pass
348+
class A:
349+
def hello(self, name):
350+
return "Hello %s" % name
351+
root = Root()
352+
root.a = A()
353+
result = xmlrpc.traverse(root, 'a.hello', ["there"])
354+
self.assertEqual(result, "Hello there")
294355

295356
class SupervisorTransportTests(unittest.TestCase):
296357
def _getTargetClass(self):

supervisor/xmlrpc.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -428,18 +428,27 @@ def call(self, method, params):
428428
return traverse(self.rpcinterface, method, params)
429429

430430
def traverse(ob, method, params):
431-
path = method.split('.')
432-
for name in path:
433-
if name.startswith('_'):
434-
# security (don't allow things that start with an underscore to
435-
# be called remotely)
436-
raise RPCError(Faults.UNKNOWN_METHOD)
437-
ob = getattr(ob, name, None)
438-
if ob is None:
439-
raise RPCError(Faults.UNKNOWN_METHOD)
431+
dotted_parts = method.split('.')
432+
# security (CVE-2017-11610, don't allow object traversal)
433+
if len(dotted_parts) != 2:
434+
raise RPCError(Faults.UNKNOWN_METHOD)
435+
namespace, method = dotted_parts
436+
437+
# security (don't allow methods that start with an underscore to
438+
# be called remotely)
439+
if method.startswith('_'):
440+
raise RPCError(Faults.UNKNOWN_METHOD)
441+
442+
rpcinterface = getattr(ob, namespace, None)
443+
if rpcinterface is None:
444+
raise RPCError(Faults.UNKNOWN_METHOD)
445+
446+
func = getattr(rpcinterface, method, None)
447+
if not isinstance(func, types.MethodType):
448+
raise RPCError(Faults.UNKNOWN_METHOD)
440449

441450
try:
442-
return ob(*params)
451+
return func(*params)
443452
except TypeError:
444453
raise RPCError(Faults.INCORRECT_PARAMETERS)
445454

0 commit comments

Comments
 (0)