MIME-RPC [via Dave Winer] MIME-RPC is a protocol for applications written in different languages and on different platforms to communicate with each other using a public standards.
Positioned as a direct competitor to SOAP and XML-RPC. Nice.
Unfortunately, the only known implementation of MIME-RPC has a security hole that can allow attackers to execute arbitrary Python code on the server. This is not a bug in the protocol itself, merely the Python implementation. Still, not an encouraging start.
I alerted the author of this hole (described below), and he has already fixed it. The fixed version has been distributed to the MIME-RPC mailing list and posted on the MIME-RPC web site. The information below applies only to version 1.0 of mimerpc.py; later versions do not have this problem.
mimerpc.py uses the eval() function in four places to dynamically dispatch to type-specific functions: in getTypeWriter(), in getClassWriter(), and getReaderForMimeType(), and getTransport(). In my opinion, getattr() would be a better choice, since eval() allows evaluation of arbitrary expressions, while getattr() merely looks up object names. (getattr() is also much faster, but speed is not the issue here.) As it happens, three of these instances of eval() munge their input sufficiently that they are not exploitable, but getReaderForMimeType() takes its input directly from the MIME request passed from the client.
def getReaderForMimeType(mimeReaders,ctype):
if not mimeReaders.has_key(ctype):
if ctype[0:2]==”x-”: ctype=ctype[2:]
ctype=ctype.replace(”/x-”,”/”)
try: return mimeReaders[ctype]
except:
ctype=ctype.replace(”-”,”")
major,minor=ctype.split(”/”)
fnName=”%s%s%sReader”%(major,minor[0].upper(),minor[1:])
try: return eval(fnName)
except: return None
The highlighted lines are exploitable, because they do some simple string munging on the content-type passed by the client, and then eval() the entire expression. To exploit this, we need to find a content-type that cgi.py will parse correctly (long story here, but basically mimerpc.py uses cgi.py which does some preprocessing on the request before we get to the code listed above), but that, when munged in the above manner, will still yield an expression that eval() will parse and evaluate without error. We can do this with a “multipart/somethingorother” content-type, because cgi.py only checks the major “multipart/” half, but mimerpc.py uses both halves to build its eval()’d expression.
Even better, you can modify mimerpc.py itself to construct such an exploit. (The module can act as a client as well as a server.) Start an instance of the server (”python mimerpc_examples.cgi server 8000″), then make a copy of mimerpc.py to mimerpc_hacked.py. In mimerpc_hacked.py, find the listTypeWriter() function, which handles marshalling a list argument through MIME-RPC and which constructs a multipart MIME message with content-type “multipart/mixed”:
def listTypeWriter(list,mimeWriters,contents=None):
return multipart("mixed", \
mimeWriters, zip(range(len(list)),list),contents)
If we change the “mixed” argument, we can cause the server to execute arbitrary code:
def listTypeWriter(list,mimeWriters,contents=None):
return multipart(" and __import__('os').mkdir('oops') or set", \
mimeWriters, zip(range(len(list)),list),contents)
Then call a function on the server and pass a list as an argument, like this:
>>> import mimerpc_hacked
>>> s = mimerpc_hacked.RemoteServer(’http://localhost:8000/’)
>>> s.myFunc(1, ['a','b'], color=”green”)
The second argument is a list, which means it goes through our hacked listTypeWriter() function, which constructs our exploit. When the server tries to unmarshal this argument, getReaderForMimeType() constructs and eval()’s the following expression:
multipart and __import__('os').mkdir('oops') or setReader
This is a valid Python expression (both ‘multipart’ and ’setReader’ are functions defined in mimerpc.py). Evaluating the expression imports the ‘os’ module, calls os.mkdir(’oops’), and returns None. This will raise an exception later in mimerpc.py, since it was expecting eval() to return a function instead of None, but it admirably recovers and simply returns an empty result to the client and keeps processing. However, the damage is already done: we have successfully created a directory on the server. It doesn’t take much imagination to see how this exploit could be much, much worse; the full power of Python is available to the attacker.
Moral(s) of the story: don’t use eval() when you can use getattr() or some other method. Don’t be fancy when you can be simple. Use specific functions instead of generic ones. And whatever you do, never, ever trust the client.

