Tuesday, March 06, 2007

Ruby, Python, and an XML-RPC Server Arbitrary Shell Command Execution Flaw

So I've been playing around with the Ruby XML-RPC APIs (and finally got them talking with Python, but more on that later) and I was sort of shocked to see that a "nasty security hole" described on the xmlrpc4r site wiki (or whatever it is) by Brian Candler is still around in the most up to date versions of Ruby 1.8.x in OpenBSD 4.0 and Ubuntu Dapper LTS. Furthermore, this particular issue does not appear to be included in any of the vulnerability databases such as NVD, CERT, or Secunia. Nor is it listed on the Ruby site. Although there is a workaround, the vulnerable code is the dominant and most common approach described in tutorials--therefore probably what is used by newbies. The "secure way" is ugly and unnatural.

Since Ruby seem to be about the only HTTP implementation that has HTTP Digest Authentication on the server side, I was thinking about using it (instead of Python, which I obviously prefer) for a small VMware monitoring app but this (not to mention Ruby/Ruby XML-RPC's relatively poor security track record and I know there have been a couple of Python issues as well) has got me reconsidering this approach. Unlike Perl, I've never had any strong opposition to using Ruby and I always considered them roughly equivalent.

The Perl-ish "there is more than one way to do it" certainly can produce less readable and could result is less secure code, both in terms of Ruby itself and apps developed with Ruby. I wouldn't want to come up with any strong conclusions on the relative insecurity of the languages or APIs, but the relative attack surface of the two XML-RPC implementations is worth a peek.

So here is a quick Python script to enumerate available methods on a XML-RPC web service that has introspection enabled.
import xmlrpclib
server = xmlrpclib.Server("http://127.0.0.1:8080")
for method in server.system.listMethods():
print method
And if we run it against the example XML-RPC server script
require "xmlrpc/server"
s = XMLRPC::Server.new
class MyHandler
def sumAndDifference(a, b)
{ "sum" => a + b, "difference" => a - b }
end
end
s.add_introspection
s.add_handler("sample", MyHandler.new)
s.serve
We get a whole lot...
franz-g4:~ mdfranz$ python client.py
system.listMethods
system.methodSignature
system.methodHelp
sample.to_a
sample.respond_to?
sample.type
sample.dclone
sample.sumAndDifference
sample.protected_methods
sample.eql?
sample.instance_variable_set
sample.is_a?
sample.hash
sample.to_s
sample.send
sample.class
sample.tainted?
sample.private_methods
sample.__send__
sample.untaint
sample.id
sample.inspect
sample.instance_eval
sample.clone
sample.public_methods
sample.extend
sample.freeze
sample.display
sample.__id__
sample.method
sample.==
sample.methods
sample.===
sample.nil?
sample.dup
sample.instance_variables
sample.instance_of?
sample.object_id
sample.=~
sample.singleton_methods
sample.equal?
sample.taint
sample.frozen?
sample.instance_variable_get
sample.kind_of?
Compared to a simple Python server script (which actually would have its own code execution issues pre 2.4.1/2.3.5 since I didn't use dispatch)
from SimpleXMLRPCServer import *
class HeyJim(object):
def __init__(self):
pass
def bobo(spaz):
return "x" * int(spaz)
server = SimpleXMLRPCServer(("localhost",8000))
server.register_instance(HeyJim)
server.register_introspection_functions()
server.serve_forever()
Which only produces...
franz-g4:~ mdfranz$ python client.py
bobo
system.listMethods
system.methodHelp
system.methodSignature
I initially used the register_function instead of register_method, but the results were the same. I wanted it to be a fair comparison. Perhaps one reaons is Ruby's method/attribute sloppiness. It exposes everything. IIRC, Python only allows methods to be exposed on objects.

1 comment:

pvo said...

This is actually still vulnerable in
ruby 1.8.6 (2007-09-24 patchlevel 111) [universal-darwin9.0]
on OSX

*and*

ruby 1.8.6 (2007-06-07 patchlevel 36) [i486-linux]
on Gusty 7.10.