七、CommonCollection2
本篇文章基础有:
队列
优先队列
分析
代码分析
首先还是先看下代码
这个和1、6两条链有点不一样了,这里没用Map,而是Queue
看下第一行代码
public static Object createTemplatesImpl(String command) throws Exception {
return Boolean.parseBoolean(System.getProperty("properXalan", "false"))
?
createTemplatesImpl(
command,
Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")
)
:
createTemplatesImpl(
command,
TemplatesImpl.class,
AbstractTranslet.class,
TransformerFactoryImpl.class
);
}
首先获得系统属性值properXalan
如果返回值为 true 则通过反射创建这三个Class对象
如果返回值为 false或者没有找到 properXalan 属性 , 则直接调用三个类的Class对象(java会自动为每一个类创建Class对象,使用类名.class就能获取到)
properXalan是什么?
不知道,网上搜这个全是关于这条链的分析,但都是互相抄来抄去,没人介绍properXalan,这个问题先跳过
继续看createTemplatesImpl方法
看不懂,反正是将cmd命令写入了templates
继续看下面代码
InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
PriorityQueue<Object> queue = new PriorityQueue(2, new TransformingComparator(transformer));
创建了一个InvokerTransformer,反射调用的方法是toString
创建了一个优先队列,大小为2,第二个参数看起来像是比较的,创建了一个用来比较优先级的Transformer
看下TransformingComparator构造函数
如图,将上面构造的transformer对象保存在了TransformingComparator对象的transformer变量里
然后在队列里连续添加了两个1
然后修改transformer的iMethodName为newTransformer,即transformer反射调用的是newTransformer方法
然后通过反射获取了queue对象的queue数组
将第一个元素设为templates
第二个元素为1
然后,,这,就结束了
好吧,再调试一遍反序列化的过程
调试分析反序列化过程
代码如下,反序列化时虽然报错,但是成功弹出计算器了
之前已经调试过很多次ObjectInputStream的readObject方法了
直接看ObjectInputStream的readSerialData吧,在这个方法内,通过递归反序列化出完整的对象
如图,通过desc可以看见当前正在被反序列化的对象
经过反复调试,最终发现
PriorityQueue对象的readObject方法内调用了heapify(这个函数是用来把数组转堆的,方便排序),导致弹出计算器
看一下代码
代码执行到了siftDownUsingComparator
如图
可以看见,这里的cmp就是之前代码里的TransformingComparator对象,
如图,调用了transform方法
回想一下,这个transformer对象是InvokerTransformer类型,反射方法是newTransformer
也就是这里会调用obj1的newTransformer方法,而obj是通过Gadgets.createTemplatesImpl创建的,包含了命令的对象
如图,就是这句代码,执行后弹出计算器
好,总结下
总结
首先是PriorityQueue在反序列化时
如图,会先把队列内的数据存到数组中,然后调用heapify方法,将数据按堆排列
在构建堆的过程中,会通过TransformingComparator比较两个元素,
而TransformingComparator会对比较的两个数据通过transformer进行处理,而transformer正是前面构造好的,反射调用newTransformer方法的InvokerTransformer对象
而队列中的两个元素分别是TemplatesImpl对象和1
在对他们比较时,InvokerTransformer对象调用了TemplatesImpl的newTransformer方法,导致rce
所以,这条利用链分为几部分
一、构造TemplatesImpl对象,在被调用newTransformer方法时,就会导致rce
二、构造InvokerTransformer对象,通过反射调用对象的newTransformer方法
三、构造PriorityQueue对象,PriorityQueue对象在反序列化时需要构建堆,就需要比较元素,比较元素时通过TransformingComparator对象比较,而TransformingComparator对象比较时会调用Transformer对象,而Transformer对象是第二步构造的InvokerTransformer对象
所以利用链是:
PriorityQueue的readObject->TransformingComparator的cmpare->InvokerTransformer通过反射调用TemplatesImpl的newTransformer方法->导致rce
填坑
上面还有个问题没解决,TemplatesImpl对象是什么?怎么构建的?为什么调用它的newTransformer方法会命令执行
写代码调试一下,,结果,,TemplatesImp对象不能调用newTransformer方法?
代码改写一下,可以运行了,但是不会弹计算器
反复对比刚才反序列化的场景,感觉一毛一样啊
又仔细对比了下
有两个TemplatesImpl包,一个是ysoserial自带的,一个是jdk的
而jdk的包不允许直接import(好像是JDK 9开始的模块封装)
看一下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl包,,结果里面有newTransformer方法,而且是public,看来是因为模块封装,导致不能调用的
再调试下反序列化过程,发现红框这里弹出了计算器,,看起来是创建了一个对象,,对象名是一串数字
而在执行蓝框之前,_class为null
看一下defineTransletClasses方法,在这里,从_bytecodes中加载了_class
回想一下,之前在Gadgets.createTemplatesImpl方法中设置过_bytecodes
所以是因为TemplateImpl的getTransletInstance方法,实例化了Gadgets.createTemplatesImpl中构造的_bytecodes,导致rce
所以现在看下Gadgets.createTemplatesImpl,如何构造的_bytecodes
之前这里没看懂,调试了下ClassPool对象,,越看越复杂,,网上查了下,原来这个是属于Javassist库
Javassist库是一个Java字节码类库,java程序编译好后都是后缀为.class文件,这就是由字节码组成的
通过Javassist库可以处理字节码
现在再来看下这段代码,它是如何生成字节码
ClassPool pool = ClassPool.getDefault(); // getDefault 方法返回一个单例模式的ClassPool对象
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 把AbstractTranslet类的路径添加进pool对象(一会下面创建AbstractTranslet对象会用到)
CtClass clazz = pool.makeClass(String.valueOf(System.nanoTime())); // 创建一个空类,类名是系统时间(纳秒)
String cmd = DirectiveProcessor.process(command); // 通过需要执行的命令,构造了一段代码
clazz.makeClassInitializer().insertAfter(cmd); // 通过makeClassInitializer创建了静态代码块,并将上一步构造好的代码,插入到静态代码块
CtClass superC = pool.get(AbstractTranslet.class.getName()); // 通过AbstractTranslet创建了CtClass对象
clazz.setSuperclass(superC); // 设置包含命令执行代码的对象的父类为AbstractTranslet
byte[] classBytes = clazz.toBytecode(); // 包命令执行对象转bytecode即字节码
这个CtClass和Class有点像,都是用来储存类信息的,但是Class对象正常是不能由开发者创建的,是由jvm创建的,开发者只能通过.class属性值或getClass、forName方法去获取到
分析下过程
这里创建了CtClass对象,然后写入静态代码块,设置父类为AbstractTranslet,然后转字节码
静态代码块在讲反射时讲过,当加载类时,会先执行静态代码块里的内容
设置父类为AbstractTranslet是为了在TemplatesImpl中可以执行newTransformer时可以正常创建AbstractTranslet对象
还有个问题就是,怎么构造静态代码块里内容的?
看下DirectiveProcessor.process的代码
构造好了之后是这样的
java.lang.Runtime.getRuntime().exec("calc");
ok,至此,完全明白了CommonsCollections2利用链的利用细节
现在想一下,为什么我刚开始写的代码不能触发rce?
因为这里构造的templates对象,虽然包含了字节码,但是字节码是通过反射,illegal的方式写入的,所以这个templates对象是不完整的
templates对象内_tfactory的值为null,在加载字节码时,会用到它,所以这时就会异常退出
而templates对象反序列化时,不仅读取出了字节码信息,还创建了_tfactory对象(反序列化时创建了完整的templates对象)
再总结下
再总结
首先通过Javassist库,创建一个字节码类,这个类包含静态代码块,里面是命令执行代码,它的父类是AbstractTranslet
创建个空的TemplatesImpl对象,把它的_bytecode值改为1中的字节码
构造InvokeTransformer对象,可以通过反射调用newTransformer方法
构造优先级队列,添加两个元素,创建TransformingComparator对象,而TransformingComparator对象内部实现是通过InvokeTransformer对象对象
所以在反序列化优先级队列时,,会重新构建堆->调用TransformingComparator的compare方法->InvokeTransformer的transform方法->TemplatesImpl的newTransformer方法->getTransletInstance方法加载了字节码,实例化对象->加载了字节码中的恶意静态代码块,导致rce
ok,至此CommonCollection2学习完毕