Python Pickle Deserialization Worms
Python’s pickle module converts objects to and from a byte stream. Because unpickling reconstructs objects by calling arbitrary callables specified in the byte stream, pickle.loads() on attacker-controlled data is equivalent to arbitrary code execution. The official documentation warns: “Never unpickle data received from an untrusted or unauthenticated source.” 1
A pickle worm is a self-propagating payload that, when deserialized, injects itself into other objects or the application runtime so that subsequent serializations carry the malicious payload forward — worm-like propagation through a system that pickles and unpickles objects.
How pickle enables code execution
pickle.dumps(obj) calls obj.__reduce__() to determine how to reconstruct the object. __reduce__() returns a tuple (callable, args); pickle.loads() later calls callable(*args) to rebuild the object. An attacker who controls the pickled bytes can make __reduce__() return any callable — os.system, subprocess.call, exec, eval — with any arguments. Stephen Checkoway’s canonical cos\nsystem\n(S'/bin/sh'\ntR. payload spawns a shell on unpickle. 2
Nelson Elhage walks through crafting malicious pickles by hand, including encoding arbitrary functions with marshal and base64 to survive deserialization even when the target application expects a specific class shape. 3
Worm mechanics
A pickle worm goes beyond one-shot RCE. The payload is constructed so that, on deserialization, it:
- Executes attacker code (e.g., opens a reverse shell, prints a message).
- Patches the host application’s classes (typically the serialization method, e.g., a
serializeorto_dictmethod) so that every object the application later serializes embeds the worm’s pickled bytes as a field. - When another node or process unpickles one of those objects, the cycle repeats — the infection spreads with the data.
The propagation vector is the application’s own object-serialization channel: any peer that trusts pickled data from the infected host becomes infected in turn. This is the pickle-specific instance of the general principle that deserialization of attacker-controlled data is code execution — the same class of bug as Java ObjectInputStream deserialization (ysoserial) and PHP unserialize() object injection.
Defenses
- Never unpickle untrusted data. Use
json,msgpack, orprotobuffor untrusted input. - If pickle is unavoidable, restrict globals with a custom
Unpickler.find_class()that allows only explicitly whitelisted classes — see the Restricting Globals section of the docs. 4 - Sign pickled data (HMAC) so integrity is verified before deserialization.
- Static scanners like picklescan detect known-bad globals in pickle files, though they have bypasses — see GHSA-655q-fx9r-782v. 5
- For ML model files, prefer safetensors or ONNX over pickle-based formats (PyTorch
.ptfiles are pickles).
See also
- python — hub page with a basic pickle RCE example
- xss-attacks — another injection-class vulnerability
- msfvenom — generating payloads that a pickle exploit might deliver
Sources
- Python pickle module documentation
- Stephen Checkoway — Arbitrary code execution with Python pickles
- Nelson Elhage — Exploiting misuse of Python’s pickle
- David Hamann — Exploiting Python pickle
- GHSA-655q-fx9r-782v — Picklescan RCE bypass