0CTF 2022 - Java deserialization

I played 0ctf with Super Guesser and mainly focused on 4 web challs out of which I was able to solve 2 of them but couldnt solve the other 2 JAVA challs, so I thought this could be a good time to solve the leftover challs and learn JAVA alongside. This is writeup for hessian-onlyJdk, this helps understanding java deserialization

hessian-onlyjdk

Introduction & Setup

We were given a java application (Source code attached)arrow-up-right which takes user input as POST body and uses the hessian Library to parse the POST Input. The Vulnerability was straight forward to discover from the source : Index.java

try {
    final InputStream is = t.getRequestBody(); // `is` is POST body
    final Hessian2Input input = new Hessian2Input(is); 
    input.readObject();
}
catch (Exception e) {
    e.printStackTrace();
    response = "oops! something is wrong";
}

here, input.readObject() is vulnerable to Insecure deserialization and our goal was to find exploit(Java Gadget chain), send it as POST body and achieve RCE. We can try to use gadget chains from ysoserial and other places but none worked, so we will have to create our own gadget chain now.

Learning JAVA to find Gadget chain

First step was to understand how JAVA deserialization works. I came accorss this nice talk from blackhatarrow-up-right

The basic idea of deserialization is follows:

  1. The code input.readObject() means, whatever Object we send in the POST body, it will deserialize it and next the code calls readObject magic method on it and we as attacker wanna make sure we achive rce when code calls this method. Now we cannot send any object ofcourse, the Object we are sending should exist in classPath of the running code. ClassPath is nothing but list of available classes.

Example, suppose there is a class myClass.java somewhere in the code like this

Now if the main program either belongs to same package or the above class’s are included while running the JAVA application, we should be able to access the Class’s, otherwise we wont be able to access it. Lets say this class is included in classpath while runing the program like:

we can create a payload like below to get RCE

Since Vulnclass has Serialiable implemented, we should be able to serialize this class. When any code runs readobject on it, the code should be able to recover the Object of this class with instance variables(public/private) set to what we wanted it to be.

Finding Gadget chain to exploit Hessian Library

As seen above, now we know how to create Gadget chain , next step is to find which code to look the Gadget chain at. Like I said , all the Serializable class’s accessible to the application are possible victim, this includes

  1. Entire JAVA JDK

  2. Classe’s in classpath’s included while running app (libraries, jar files etc.)

The application above uses quite few libraries and JDK version 1.8(also known as JDK 8arrow-up-right) So our goal would be to find Gadget chain in them to achieve RCE.

Here’s the Gadget chain we will be using to attack (this is written in kotlin)

Lets decouple the chain shall we:

Part 1: getting the methods to execute

We will be using reflection in kotlin to get methods

Part 2. Setting up command executing function which will get us RCE

Our goal is to call SwingLazyValue.createValue since it has the following code:

It takes classname, methodname and string array and execute m.invoke(c, args); on it. we can set m to be Runtime.exec but for somereason that didnt work, so we have to wrap Runtime.exec around another MethodUtil.invoke like below

The above is same as

Like in the pic above, now if we do c.createValue, we will get command execution from the line m.invoke(c, args); (m here is Methodutil, c is “invoke” and args is the [Runtime.exec, cmd])

Part 3: setting up chain to reach the reach the SwingLazyValue.createValue function

Here we can see, the getFromHashTable function of UIDefaults class calls createValue. The function get value by doing super.get(key) and if the returned value is LazyValue(i.e Object), it just calls Object.createValue on it, and that what we wanted, soo,

Now if in code there is a call to myUiDefaultObject.getFromHashtable("_"), it will call (SwingLazyValue).createValue(this); and we will get RCE. So we need a way to now call myUiDefaultObject.getFromHashtable. Actually the function getFromHashtable is called by .get function of UIDefaults

We need to find a way to call UIDefaults.get("_"). Now this definately looks achievable. Actually, if we wrap UIDefaults object around HashMap, when a HashMap is deserialized, it calls .get on each key to check if key already exist in map or not. So now we just have to wrap the UIDefaults Object around HashMap and it will call .get on keys.

Here’s how .get is called on hashmap keys:

On deserialization, hashmap calls readObject , this calls readObject on keys & Values and does putVal

The putval function calls key.equals(k) (here key is Object of class UIDefaults):

The UIDefaults class doesnt itself have equals function but since it extends HashTable, we can find the equals function inside HashTable class as below

we can see here it calls .get on the key (ie UIDefaults). So the chain looks like

SUMMARY: When the hashmap is deserialized,

  1. It will call putVal(key, val)

    IMP: key here is UIDefaults object

    since we have 2 keys, it will call putval for 1st key and then the 2nd key. While doing it for 2nd key, since the hashmap is not empty, it will check if the key aready exist or not by calling key.equals(key2)

  2. key.equals(k2) call equals method of Hashtable class. The Hashtable or Uidefaults.equals() calls Uidefaults.get("_")

  3. UiDefaults.get calls getFromHashtable function on the value of UiDefaults.get("_") which is SwingLazyValue payload and thus gives us RCE.

Putting it all together in code:

This would be the complete chain. The hashMap object now obtained, if we call hashMap.readObj() on it, the deserialization chain will start and we should get RCE.

Last updated