Insecure deserialization — why "just parse the data" can execute code
Some serialization formats can represent more than plain data — they can encode instructions for reconstructing arbitrary objects, including ones whose construction itself runs real code, which means deserializing untrusted data can mean executing untrusted code, not just reading untrusted values.
4 min read
Why this is a genuinely different risk than parsing JSON
import json
data = json.loads(untrusted_input) # SAFE — JSON can only represent plain data:
# strings, numbers, booleans, null, arrays, objects
# there is NO way to encode "run this code" in JSON itselfPlain JSON deserialization is safe from this specific risk, structurally — the JSON format itself has no syntax capable of representing "construct an instance of this arbitrary class" or "call this function," only plain data values. This lesson's risk applies to formats and libraries that go beyond plain data — ones explicitly designed to serialize and reconstruct arbitrary objects, including their type information.
The vulnerability: some formats can reconstruct ANY object, including ones with dangerous side effects
import pickle
data = pickle.loads(untrusted_bytes) # DANGEROUS — pickle can reconstruct ARBITRARY Python objects# A malicious pickle payload can be crafted so that DESERIALIZING it alone
# — before the resulting object is ever actually "used" for anything —
# triggers a class's __reduce__ method, which pickle calls automatically
# during reconstruction, and which can be made to run ARBITRARY CODE
class Exploit:
def __reduce__(self):
return (os.system, ("rm -rf /",)) # this runs the MOMENT this object is deserializedPython's pickle module (and similar object-serialization formats in other languages — Java's native serialization, PHP's unserialize) is explicitly designed to reconstruct arbitrary objects, including calling methods during that reconstruction process — __reduce__ is a real, legitimate Python protocol method pickle calls to figure out how to rebuild an object, and nothing stops a maliciously crafted payload from making that method execute genuinely arbitrary code, including OS-level commands, the instant the payload is deserialized. This isn't a bug in pickle being "used wrong" — it's pickle doing exactly what it was designed to do, applied to data an attacker controls instead of data the application itself created.
The real, recurring rule: never deserialize untrusted data with a format capable of arbitrary object reconstruction
# NEVER do this with data from an untrusted source (a request body,
# a file a user uploaded, a message from an external queue you don't
# fully control):
data = pickle.loads(request_body)
# SAFE alternative for untrusted data — use a format that can ONLY
# represent plain data, with no code-execution capability at all:
data = json.loads(request_body)The actual, deliberate rule: formats capable of reconstructing arbitrary objects (pickle, Java's native serialization, and similar) should never be used to deserialize data that originates from outside the application's own trust boundary (recall the first lesson in this domain) — they're genuinely useful and safe for data the application itself produced and controls entirely (an internal cache, a message an application sent to itself), but dangerous the moment the serialized bytes could have been crafted by an attacker. For any data crossing a real trust boundary, a plain-data format like JSON is the structurally safe choice, precisely because it has no equivalent capability to trigger code execution during parsing.
Where this shows up in real, practical situations
- A session stored as a pickled object, if an attacker can forge or
tamper with the session cookie's raw bytes
- A message queue where messages are pickled Python objects, if the
queue itself (or anything feeding it) isn't fully trusted
- A caching layer using pickle for serialization, if cache poisoning
from an untrusted source is even remotely possible
This isn't a purely theoretical concern — real, documented vulnerabilities have come from exactly these patterns: session data serialized with pickle and stored in a cookie the client can tamper with, message queues accepting pickled payloads from sources that turned out to be less trusted than assumed, and caching layers where an attacker found a way to poison cached, pickled data. Each case has the same underlying shape: a genuinely convenient serialization format used somewhere data could realistically cross a trust boundary, without that boundary being explicitly considered at the time the format was chosen.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is deserializing untrusted JSON structurally safe, while deserializing untrusted pickle data is not?
2. How can deserializing a malicious pickle payload execute arbitrary code, before the resulting object is even used?
3. What's the actual, deliberate rule for choosing a serialization format across a trust boundary?