flawopen.com/Insecure Deserialization/Python pickle security
Is Python pickle.load() safe for untrusted data?
No. Treat pickle like a program sent inside a parcel, not like a passive data record: loading it can invoke reconstruction behavior before your application validates the resulting object. Use a data-only format for inputs that cross a trust boundary.
import pickle # Never accept pickle bytes from a request, upload, # shared cache, or other untrusted source. obj = pickle.loads(untrusted_bytes)
import json
def parse_profile(raw: bytes) -> dict[str, str]:
if len(raw) > 65_536:
raise ValueError("Payload too large")
try:
value = json.loads(raw)
except (json.JSONDecodeError, UnicodeDecodeError):
raise ValueError("Invalid JSON") from None
if not isinstance(value, dict) or set(value) - {"display_name", "theme"}:
raise ValueError("Invalid profile")
name = value.get("display_name")
theme = value.get("theme")
if not isinstance(name, str) or len(name) > 80:
raise ValueError("Invalid display name")
if theme not in {"light", "dark"}:
raise ValueError("Invalid theme")
return {"display_name": name, "theme": theme}
- Pickle stream:
- A Python-specific byte sequence that describes object reconstruction, including references to globals and reconstruction behavior.
- Unpickling:
- Interpreting a stream and rebuilding its object graph; reconstruction may invoke callable behavior.
- Trust boundary:
- Any point where bytes can be controlled or modified by a user, another service, an upload, or a shared store.
- CWE-502:
- Deserialization of untrusted data; impact depends on whether an attacker can supply or tamper with data that the application loads.
Untrusted bytes reach a pickle sink
An upload, request, cache entry, or artifact carries attacker-controlled or tampered pickle data into the process.
The unpickler resolves globals
The interpreter processes reconstruction instructions; find_class() can import and return named globals, including functions.
Loading triggers behavior
Instructions can invoke callables while load() or loads() runs, before application checks on the returned object.
Impact follows process authority
Behavior runs with the Python process permissions. Network reach, secrets, filesystem access, and sandboxing determine the blast radius.
Why pickle loading crosses a code-execution boundary
A pickle stream can name globals and reconstruction callables. During unpickling, Python’s Unpickler.find_class() resolves globals, including functions; opcodes can then invoke reconstruction behavior. The dangerous step is pickle.load() or pickle.loads() itself—there is no later, separate execute call. The process runs resulting code with its operating-system permissions. This is the format’s documented behavior, not a parser bug; do not use pickle as an interchange format for untrusted input.
What JSON fixes—and what it does not
Python documents that unpickling untrusted or tampered data can execute arbitrary code, while JSON decoding does not itself create that arbitrary-code-execution path. JSON is not automatic input validation: cap payload size, require the expected top-level type and fields, validate each value against a schema, and apply authorization separately. If a legacy workflow must load pickle, authenticate the exact bytes with a protected key before unpickling; a signature proves origin and integrity only under its key assumptions and does not make malicious data from a trusted signer safe. Prefer migration to a data-only format.
- ✓Remove
pickle.load()andpickle.loads()from paths that consume requests, uploads, shared caches, or artifacts of uncertain provenance. - ✓Use JSON or another data-only encoding, then enforce a payload-size limit, schema, allowed fields, value bounds, and endpoint authorization.
- ✓If a trusted internal workflow temporarily retains pickle, verify an HMAC or signature over the exact bytes before loading, protect the verification key, and restrict the loader process privileges.
- ✓Search repositories and data flows for pickle consumers; classify each source by who can write or tamper with it, then migrate or isolate unsafe consumers.
Frequently asked questions
Overriding find_class() can reduce risk by restricting globals, but proving a restricted loader safe across protocols and reachable behavior is difficult. Do not treat an allow-list as the default defense for user-controlled bytes; prefer a data-only format.
Yes. JSON avoids pickle-style arbitrary code execution during decoding, but applications must still validate type, fields, sizes, value ranges, and authorization. Treat JSON parsing and business-rule validation as separate steps.
Primary references
- Python documentation — pickle security warning and restricted globals
- Python documentation — JSON decoder
- CWE-502 — Deserialization of Untrusted Data