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:

  1. Executes attacker code (e.g., opens a reverse shell, prints a message).
  2. Patches the host application’s classes (typically the serialization method, e.g., a serialize or to_dict method) so that every object the application later serializes embeds the worm’s pickled bytes as a field.
  3. 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, or protobuf for 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 .pt files 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

Footnotes

  1. pickle — Python object serialization — Python 3.14.6 documentation

  2. 2013 — Arbitrary code execution with Python pickles

  3. 2011 — Exploiting misuse of Python’s pickle

  4. pickle — Python object serialization — Python 3.14.6 documentation

  5. 2025 — Picklescan Allows Remote Code Execution via Malicious Pickle File Bypassing Static Analysis