| name | prevent-insecure-deserialization |
| description | Use when deserializing data from untrusted sources — user-submitted cookies, API request bodies, message queue payloads, or any format that reconstructs objects (pickle, Java serialization, YAML, PHP serialize). |
| source | OWASP Deserialization Cheat Sheet (owasp.org/www-project-cheat-sheets); OWASP Top 10 2021 A08; CWE-502; NIST NVD deserialization CVE history |
| tags | ["security","owasp","deserialization","pickle","java-serialization","rce","developer"] |
Prevent Insecure Deserialization
Never deserialize untrusted data using native serialization formats — use data-only formats (JSON, Protobuf) with schema validation, or enforce strict type allowlists when native serialization is unavoidable.
Why This Is Best Practice
Adopted by: OWASP Top 10 2021 A08 (Software and Data Integrity Failures) covers insecure deserialization. Oracle, Apache, Red Hat, and Cisco have all patched critical RCE vulnerabilities caused by Java deserialization (CVE-2015-4852, CVE-2016-0792, etc). Node.js node-serialize, Python pickle, PHP unserialize, and Ruby Marshal.load are all documented RCE vectors when given untrusted input.
Impact: Apache Commons Collections deserialization RCE (2015) affected WebLogic, JBoss, Jenkins, and dozens of Java application servers — enabling remote code execution with zero authentication. Python pickle.loads(user_input) is equivalent to eval(user_input) — it executes arbitrary code. GitHub's 2017 Enterprise Server RCE was caused by Ruby Marshal deserialization. Insecure deserialization is rated CVSS 9.8 (Critical) in most CVEs.
Why best: Denylisting dangerous classes (Java serialization filters, etc.) is the alternative — it requires knowing every gadget chain in advance and fails when new chains are discovered. Using data-only formats (JSON) eliminates the vulnerability class: JSON can represent data structures but cannot encode executable code.
Sources: OWASP Deserialization Cheat Sheet; Apache Commons Collections CVE-2015-4852; CWE-502; Java Serialization "AppSecCali 2015 — Marshalling Pickles" (Frohoff & Lawrence)
Steps
-
Prefer data-only serialization formats — JSON, XML (with XXE disabled), MessagePack, Protobuf, or Avro. These encode data, not executable objects.
import pickle
obj = pickle.loads(user_input)
import json
data = json.loads(user_input)
-
If native serialization is required, validate/authenticate the data before deserializing:
Sign the serialized blob with an HMAC and verify the signature before deserializing:
import hmac, hashlib, pickle
SECRET = b'server-secret-key-min-32-bytes-here'
def serialize_signed(obj):
payload = pickle.dumps(obj)
sig = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
return sig + ':' + payload.hex()
def deserialize_verified(signed_data):
sig, hex_payload = signed_data.split(':', 1)
payload = bytes.fromhex(hex_payload)
expected = hmac.new(SECRET, payload, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
raise ValueError("Signature mismatch — data tampered")
return pickle.loads(payload)
-
Java — implement serialization filters (JEP 290):
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.SafeClass;!*"
);
(inputStream);
ois.setObjectInputFilter(filter);
ois.readObject();
Rules
pickle.loads, yaml.load (without Loader=yaml.SafeLoader), Marshal.load, and Java's ObjectInputStream are unsafe on untrusted input — no exceptions.
- YAML
yaml.load() without SafeLoader allows arbitrary Python object construction — always use yaml.safe_load().
- Signing serialized data prevents tampering but does not prevent attacks if the signing key is compromised — defense-in-depth (type allowlists) is still needed.
- Deserialization gadget chains evolve — new chains are discovered in existing libraries regularly; allowlisting is more durable than denylisting.
Common Mistakes
- Using
yaml.load(input) instead of yaml.safe_load(input) — YAML's default loader supports Python object tags and executes arbitrary constructors.
- Base64-encoding the serialized blob and assuming it's safe — encoding != encryption != authentication. Attackers can decode, modify, and re-encode.
- Java
ObjectInputStream without filters — the default behavior accepts all classes, enabling gadget-chain RCE.
- Trusting signed JWTs for complex object reconstruction — JWT payload should contain simple claims, not serialized objects.