Link Search Menu Expand Document

Unsafe Deserialization in Python

Vulnerable example

Python provides a native solution for this problem - the pickle library. The following Flask endpoint provides an example where untrusted data is fed into the pickle.loads function:

import pickle

@app.route("/import_object", methods=['POST'])
def import_object():
    data = request.files.get('user_file').read()
    user_object = pickle.loads(data)
    store_in_database(user_object)
    return 'OK'

A malicious user could craft a payload that evaluates as code when unpickled. The Python program below outputs a payload that executes a system command when processed by pickle.loads:

import pickle
import os

class Pickle(object):
    def __reduce__(self):
        return os.system, ('id > /tmp/proof',)

o = Pickle()
p = pickle.dumps(o)
print(p)

The __reduce__ method provides the logic to unserialize/serialize the object. When a tuple is returned, the first element is a callable, and the second represents its argument. Thus, it is possible to execute system commands by using the os.system function. In the above case, the payload writes the output of the id command to /tmp/proof. Here is an example:

sf@secureflag.com:~$ python3 generate.py
b'\x80\x03cposix\nsystem\nq\x00X\x0f\x00\x00\x00id > /tmp/proofq\x01\x85q\x02Rq\x03.'
sf@secureflag.com:~$ python3
>>> import pickle
>>> pickle.loads(b'\x80\x03cposix\nsystem\nq\x00X\x0f\x00\x00\x00id > /tmp/proofq\x01\x85q\x02Rq\x03.')
0
sf@secureflag.com:~$ cat /tmp/proof
uid=1000(sf) gid=1000(sf) groups=1000(sf)

Prevention

The pickle library’s documentation discourages the unpickling of untrusted data and suggests using data-only serialization formats such as JSON.

If you really need to unserialize content from an untrusted source, consider implementing a message authentication code (MAC) to ensure the data integrity of the payload.

References

OWASP - Deserialization Cheat Sheet Wikipedia - Serialization