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).
Scanned 9/8/2026
Install to Claude Code
npx -y skills add jeffreytse/grimoire-core --skill prevent-insecure-deserialization --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Prevent Insecure Deserialization?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/jeffreytse-prevent-insecure-deserialization)More formats (shields.io, HTML) on the badges page.
---
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
1. **Prefer data-only serialization formats** — JSON, XML (with XXE disabled), MessagePack, Protobuf, or Avro. These encode data, not executable objects.
```python
# BAD — pickle executes arbitrary code on load
import pickle
obj = pickle.loads(user_input)
# GOOD — JSON only represents data
import json
data = json.loads(user_input) # safe; validate schema after
```
2. **If native serialization is required, validate/authenticate the data before deserializing**:
Sign the serialized blob with an HMAC and verify the signature before deserializing:
```python
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)
```
3. **Java — implement serialization filters (JEP 290)**:
```java
// Java 9+ serialization filter — allowlist only expected classes
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.SafeClass;!*" // allow only SafeClass, reject all others
);
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(filter);
Object obj = ois.readObject();
```
Alternatively, use safer alternatives: Jackson (with `@JsonTypeInfo` restrictions), Kryo with registered-classes-only mode, or FST with class registration.
4. **PHP — avoid `unserialize()` with untrusted input**:
```php
// BAD
$obj = unserialize($_COOKIE['data']);
// GOOD — use JSON
$data = json_decode($_COOKIE['data'], true);
// If unserialize is unavoidable, use allowed_classes:
$obj = unserialize($data, ['allowed_classes' => ['SafeClass']]);
```
5. **Ruby — avoid `Marshal.load` with untrusted data**:
```ruby
# BAD
obj = Marshal.load(user_supplied_string)
# GOOD — JSON
require 'json'
data = JSON.parse(user_supplied_string)
```
6. **Validate schema after deserialization** — even with safe formats, verify the structure matches expectations before using the data:
```python
import json
from jsonschema import validate
schema = {"type": "object", "properties": {"user_id": {"type": "integer"}}, "required": ["user_id"]}
data = json.loads(user_input)
validate(instance=data, schema=schema)
```
7. **Log and alert on deserialization failures** — unexpected deserialization errors often indicate exploit attempts:
```python
try:
obj = deserialize(input_data)
except (ValueError, TypeError, pickle.UnpicklingError) as e:
logger.warning("Deserialization failure from %s: %s", request.remote_addr, e)
raise BadRequest("Invalid data format")
```
## 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.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!