Skip to content

Back to the catalog

Finding Gadgets Like It's 2026 (Atredis Partners)
atredis.comwriteup

Finding Gadgets Like It's 2026 (Atredis Partners)

Atredis Partners built an LLM-driven pipeline (WALA/ASM call-graph builder, a FastAPI graph query server, Claude Code as the reasoning/validation agent, and a dynamic test/debug runner) to automate discovery of Java deserialization gadget chains against modern, hardened application servers (WebSphere, WildFly, Payara). The project found 17 confirmed chains, 6 novel, including CB1-Shaded-RCE — a chain that bypasses JPMS's TemplatesImpl restrictions via a shaded/relocated copy of Xalan's XSLTC shipped inside WildFly's JSTL jar, giving RCE on the latest WildFly on JDK 21 — plus a novel JNDI-injection chain in Payara via AttributeComparator/InjectableJMSContext. The write-up details the architecture, methodology limitations (benchmark gaming, non-determinism), and broader implications for any app/server that bundles a shaded Xalan outside java.xml, with tooling released on GitHub (atredispartners/llmchainhunter).

TL;DR Atredis used Claude Code as an autonomous agent to hunt Java deserialization gadgets, finding a novel JPMS-bypassing RCE chain (shaded TemplatesImpl) on WildFly plus new JNDI chains on Payara.
Document

What it is

Atredis's write-up establishes a concrete, checkable exposure surface: any application server or app that ships a shaded/relocated copy of Xalan's XSLTC outside the JDK's java.xml module re-enables TemplatesImpl bytecode-loading RCE that JPMS was supposed to kill in JDK 9+. The summary names CB1-Shaded-RCE on WildFly; what it can't carry are the specific namespaces to grep for, which servers ship the code but lack a trigger, and why WebSphere is immune.

How it works

JPMS blocks class Evil extends AbstractTranslet because AbstractTranslet lives in a non-exported JDK package. A shaded copy relocated into an application JAR is not JDK code, so JPMS does not apply — you extend the shaded AbstractTranslet, the shaded TransletClassLoader defines it, and Class.newInstance() runs your static initializer. On WildFly 39.0.1 the shaded copy lives in jakarta.servlet.jsp.jstl-3.0.1-jbossorg-1.jar, namespace org.eclipse.tags.shaded.org.apache.xalan, auto-loaded for every WAR. The trigger reaching it is PriorityQueue.readObject → BeanComparator.compare → TemplatesImpl.getOutputProperties, confirmed as RCE on JDK 21.0.10.

Preconditions / Affected

The RCE needs two things on the classpath: a shaded TemplatesImpl and a trigger gadget (Commons-BeanUtils, Commons-Collections 3/4, some Jackson versions, ROME, or Hibernate).

  • WildFly 39.0.1 — exploitable. Ships both the shaded JSTL Xalan and Commons-BeanUtils.
  • JBoss EAP (Jakarta EE 10+), GlassFish 7+, Payara 6+ — ship the shaded artifact but NO trigger. Exploitable only if a deployed app brings its own trigger. No bundled trigger was found in the short project.
  • Jetty (com.oracle.wls.shaded.org.apache.xalan) and TomEE (openejb.shade.org.apache.xalan) — shaded namespaces found; same "needs app-supplied trigger" caveat.
  • WebSphere (Liberty and traditional) — immune. Traditional runs IBM J9, whose com.ibm.xtq.xslt.jaxp.TemplatesImpl is not Serializable, has no _bytecodes field, and loads translets by name.
  • The standalone xalan:xalan Maven artifact (2.7.2 alone) has 112,000+ dependents; shaded copies also exist in Apache Drill, Oracle WebLogic, docx4j, and Karaf/ServiceMix/SpringSource OSGi bundles.

What to do

  • Grep deployed classpaths for shaded Xalan namespaces outside java.xml — known ones: org.eclipse.tags.shaded.org.apache.xalan, com.oracle.wls.shaded.org.apache.xalan, openejb.shade.org.apache.xalan. The authors note these are not on known deserialization blocklists.
  • Where a shaded TemplatesImpl and any trigger gadget coexist, treat it as live RCE regardless of JDK 9+/JPMS assumptions.
  • Verify RCE proof-of-approach as they did:
docker exec wildfly-deserialize-poc cat /tmp/rce-proof.txt
  • A second novel, non-RCE JNDI-injection chain in Payara (PriorityQueue → AttributeComparator → InjectableJMSContext.toString → InitialContext().lookup) uses AttributeComparator as a toString trigger — a replacement for BadAttributeValueExpException, which stopped working in JDK 18+. Worth noting if you rely on JDK 18+ as mitigation for toString-triggered chains.

Caveats

Only 1 of the 17 confirmed chains (CB1-Shaded) reached RCE; most are variations on known chains. The pipeline is non-deterministic (different results per run) and the authors caught Claude Code "gaming" the ysoserial benchmark by recognizing known gadgets, which is why they pivoted to real targets. The WildFlyDataSource-JNDI chain the LLM flagged as novel was already documented by Synacktiv (2022). Tooling and runbook: github.com/atredispartners/llmchainhunter.

Organisations named here

  • BeanComparator
  • InvokerTransformer
  • JNDI injection
  • JPMS
  • PriorityQueue-AttributeComparator-JNDI
  • Synacktiv
  • TemplatesImpl
  • TransformingComparator
  • WildFlyDataSource-JNDI

Technologies named here

  • IBM WebSphere
  • ysoserial
  • GadgetInspector
  • WildFly
  • Payara
  • Claude Code
  • WALA
  • JBoss EAP
  • Java ASM library
  • GlassFish
  • SQLite
  • Jetty
  • TomEE
  • Graph Query Server
  • xalan:xalan
  • CFR
  • Apache Drill
  • Dynamic Runner
  • Oracle WebLogic
  • RUNBOOK.md
  • docx4j
  • llmchainhunter
  • Karaf
  • ServiceMix
  • SpringSource
  • Commons-Collections
  • Jackson
  • ROME
  • Hibernate
  • Commons-BeanUtils
  • Java
  • WebSphere

Techniques named here

  • Java deserialization gadget chain construction
  • CB1-Shaded-RCE
  • PriorityQueue-AttributeComparator-JNDI chain
  • WildFlyDataSource-JNDI chain
  • CC4-BeanComparator Bridge chains
  • Type confusion for gadget discovery
  • Class Hierarchy Analysis
  • toString trigger for deserialization gadgets