GadgetProbe: Exploiting Deserialization to Brute-Force the Remote Classpath

You just found a Java deserialization bug, you ran all your ysoserial payloads, and.... you got nothing. Now what? How can you debug or build a gadget chain if you're totally blind?

SUMMARY 

Java deserialization can be a convenient and easy-to-implement transfer mechanism for sharing complex data, which despite known security risks is one of the reasons it’s still so prevalent today. Demonstrating the full impact of unsafe Java deserialization is a challenge because exploits rely on specific third-party classes being available in the remote classpath. Previously, this resulted in a Hail Mary of known exploits and if they didn’t work, we struggled to write custom exploits with limited information. That’s why I created GadgetProbe.

GadgetProbe is a tool to probe endpoints consuming Java serialized objects to identify classes, libraries, and library versions on a remote Java classpath. By taking a wordlist input of Java classes and transmitting serialized DNS callback objects, GadgetProbe enumerates what's lurking in the remote classpath.

MOTIVATION

I was recently assessing a web application and was frustrated when I couldn’t diagnose ysoserial payloads that didn’t fire.

  • Was the remote server missing dependencies?
  • Were there egress controls?
  • Why isn’t there a tool to help understand what libraries are available in the remote classpath?

With knowledge of the remote classpath, we might be able to create a custom payload or something that wasn’t in ysoserial. So, I started investigating this problem.

HOW IT WORKS

I was intrigued by the URLDNS payload in ysoserial that took advantage of the comparator behavior of the java.net.Url class. When URL classes are compared, they perform a DNS request. The figure below shows a snippet of ysoserial’s URLDNS payload:

URLDNS.java

49.  public Object getObject(final String url) throws Exception {
50.
51.      //Avoid DNS resolution during payload creation
52.      //Since the field <code>java.net.URL.handler</code> is transient, it will not be part of the serialized payload.
53.      URLStreamHandler handler = new SilentURLStreamHandler();
54.
55.      HashMap ht = new HashMap(); // HashMap that will contain the URL
56.      URL u = new URL(null, url, handler); // URL to use as the Key
57.      ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
58.
59.      Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
60.
61.      return ht;
62.  }


Figure 1
: Overriding hashcode to force URL comparison

This sparked an idea! If I could add candidate Class objects to the HashMap, then I might be able to leverage this property of URL comparison to receive an indicator of deserialization success or failure. A consistent signal of success or failure would allow me to determine if a candidate class was present in the remote classpath. After some experimentation, I constructed a Boolean primitive using a LinkedHashMap (to preserve order), with a candidate Class object and a URL pointing to my authoritative DNS server.

It worked! If an exception was triggered while deserializing the Class in question, the URL comparison operation would never occur due to the exception thrown when trying to deserialize the candidate class. Or if it did deserialize successfully, then I would get a DNS callback (e.g., org.apache.commons.collections.functors.invokertransformer.aj294jt0asdfj20.burpcollaborator.net) that confirmed its presence in the remote classpath.

However, I was faced with another problem, how would I be able to construct queries with arbitrary user-defined classes that might not be in GadgetProbe’s classpath? I used the Javassist bytecode manipulation library to create runtime-generated classes of the desired name. Although it was just an empty class with no functionality, it worked like a charm for this use case.

I rolled this functionality together into a Burp Intruder payload processor, to convert text representations of class names into serialized query objects. The extension then used Burp Collaborator to receive DNS callbacks and presented the findings in a new GadgetProbe tab, as shown in the figure below:

Findings presented in the GadgetProbe tab


Figure 2
: Findings presented in the GadgetProbe tab

I think one of the coolest parts about being able to query classes is that most major Java libraries introduce new classes to their software with each release. GadgetProbe includes an Analyzer with a handful of signatures and a wordlist for vulnerable ysoserial libraries, so that you can easily determine if you have a vulnerable version of Apache commons-collections (e.g., less than, equal to, or greater than version 4.4). However, this feature can easily be expanded to test versions for a wide variety of popular Java libraries.

In my testing, I was able to enumerate remote libraries, download the identified publicly available dependency jars, and use Ian Haken’s gadgetinspector tool to automatically find available gadgets that I could use. Combining these tools, we are now one step closer to creating a fully automated Java deserialization exploit.

The initial deserialization vector that I was investigating was a non-HTTP protocol, so I made sure to add simple Java bindings for researchers to use GadgetProbe as a Java library outside of Burp. This allows you to use the technique for more custom applications.

I hope this tool helps the community demonstrate higher impact with their deserialization vulnerabilities and helps us get one more step closer to automatic deserialization chains.

THANK YOU

Thank you to @frohoff, @gebl, and @h3xstream for the inspiration, and the team behind Handy Collaborator Burp Extension (@apps3c and Gianluca Baldi) for helpful examples for handling Burp collaborator interactions.

Check it out on GitHub! Happy hacking!