abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-zip-symlink
类别: 文件读取 可达性: 远程
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
CVE-2025-54920 锁定了 History Server 的 Jackson,使 sparkEventFromJson 只能构造 SparkListenerEvent 的子类。滚动 zipEventLogFiles 仍会通过 FileSystem.open 转储每个 listStatus 子项。LocalFileSystem 会跟随 POSIX 符号链接。在 eventlog_v2_* 中植入的子项会被打包进未认证的 GET /api/v1/applications/{id}/logs。zip 成员正文是 SHS-UID 目标。条目名称是符号链接的基名。
这不是 Jackson RCE 的重复提交。Jackson 仍然关闭。这不是主机 RCE。完整性和可用性均无影响。
| ID | 暂无 CVE |
| 类别 | 文件读取(CVE-2025-54920 受害模型遗留的滚动 zip 问题) |
| 可达性 | 远程(History 日志目录的写入者,加上对 zip 的未认证 GET) |
| CWE | CWE-59、CWE-22 |
| CVSS | 高危:7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| 产品 | Apache Spark |
| 受影响版本 | 4.2.0 标签 v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| 认证 | PR:L = 对 spark.history.fs.logDirectory 的写入权限(与 54920 相同的受害模型)。History UI ACL 默认关闭。 |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:18980 |
能够写入 spark.history.fs.logDirectory(默认 file:/tmp/spark-events)的人可以植入一个滚动目录,History Server 会将其打包为 zip。滚动写入器默认开启。History UI ACL 默认关闭。SHS 进程 UID 就是被混淆的代理。
| 权限 | 方法 / 汇聚点 | 实验验证或推断 |
|---|---|---|
| 机密性:读取 SHS UID 可打开的主机文件 | 植入 eventlog_v2_<id>/,包含有效的 appstatus_* 和 events_* JSON(SparkListenerLogStart / SparkListenerApplicationStart),以及一个目标位于日志目录之外的符号链接子项。等待扫描。未认证 GET /api/v1/applications/{id}/logs(OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open) | 实验验证。zip 成员 eventlog_v2_app-2026-0001/planted-secret 正文为 SPARK-HISTORY-ZIP-SYMLINK-WITNESS。Events 成员没有见证内容。条目名称保持为符号链接的基名,而非目标路径。 |
机密性:攻击者无法 open(660)的兄弟租户事件日志 | 同一个 zip,符号链接目标是另一个应用的日志 | 推断。相同的 FileSystem.open。未实际验证。 |
机密性:keytab、spark-env.sh、/etc/passwd(如果 SHS 可读取) | 同一个 zip | 推断。验证目标是专用的 /secret/host-secret,而非这些文件。 |
| SHS 或主机文件的完整性 | zip 被读入 HTTP 正文 | 关闭。实验验证无影响。 |
| SHS 或主机文件的可用性 | zip 不会删除或截断目标 | 关闭。实验验证无影响。 |
| 主机 RCE / Jackson gadget 构造 | JsonProtocol.sparkEventFromJson | 关闭。需要 SparkListenerEvent。本实验仅植入无害的 Event JSON。 |
条目名称中 ../ 的 zip slip | Path.getName() | 关闭。POSIX 文件名不能包含 /。条目为 eventlog_v2_<id>/<basename>。 |
resolveLogPath 的路径字符串逃逸 | new Path(logDir, attempt.logPath) | 关闭。Path 对象保持在配置的日志目录下。逃逸的是 inode。 |
| 目标的压缩重写 / 清理器删除 | spark.history.fs.eventLog.rolling.maxFilesToRetain 默认 Integer.MAX_VALUE;清理器默认关闭 | 关闭。对符号链接执行本地 fs.delete 只会解除链接,而非删除目标。 |
| 未认证作业提交 / ML 模型加载 | SECURITY.md FAQ / docs/ml-security.md | 关闭。设计如此,非本遗留问题。 |
我固定了 apache/spark v4.2.0(32f7299601108917fb01920a54e084595b7b3bf8),而非 master 或 4.3 RC。二十八次搜寻。Jackson History RCE 在此版本上已关闭(JsonProtocol.scala:965-972)。History Server 仍指向攻击者可写的日志目录,这与 CVE-2025-54920 是相同的受害模型。
zipEventLogFiles 会打包所有 files,而不仅是事件文件:
override def zipEventLogFiles(zipStream: ZipOutputStream): Unit = {
val dirEntryName = rootPath.getName + "/"
zipStream.putNextEntry(new ZipEntry(dirEntryName))
files.foreach { file =>
addFileAsZipEntry(zipStream, file.getPath, dirEntryName + file.getPath.getName)
}
}
addFileAsZipEntry 先执行 fileSystem.open,然后 transferTo。LocalFileSystem 会跟随 POSIX 符号链接。getLogDirInfo 注释说“正确处理符号链接”,然后遍历未解析的 Path 父级以获取显示名称。
已记录的弯路:将 SPARK-52381 视为整个 History 问题;将见证内容植入 events_*(实验会失败);期望 zip 条目携带目标路径;将 resolveLogPath 的前缀拼接视为逃逸点;在此版本上尝试 HiveConnection Event 类。
python3 spark-history-zip-symlink-Abraxas-Labs.py
或
cd lab
./run.sh
EventLogFileReaders.scala 滚动 zip 转储的 gadget,不是完整的 History Server,也不是官方 spark:4.2.0 镜像。它不通过 Jackson 解析 Event JSON。回环地址 127.0.0.1:18980。Compose 项目 spark-history-zip-symlink。滚动目录包含有效的 appstatus_* + events_*(无见证内容)以及 planted-secret -> /secret/host-secret。
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
仅打包 events_* / appstatus_* 子项,而非每个 listStatus 名称。使用 no-follow 打开(O_NOFOLLOW / Hadoop no-symlink 选项),并拒绝 realpath 位于 spark.history.fs.logDirectory 之外的子项。显示名称辅助函数不是 realpath 检查。
32f7299601108917fb01920a54e084595b7b3bf8)EventLogFileReaders.scala(addFileAsZipEntry 约 48-56 行,zipEventLogFiles 约 267-272 行)FsHistoryProvider.scala(writeEventLogs 约 831-856 行,resolveLogPath 约 866-896 行)OneApplicationResource.scala(GET .../logs 约 128-165 行)JsonProtocol.scala(sparkEventFromJson 约 965-972 行,Jackson 锁定)GNU Affero GPL v3.0。参见 LICENSE。仅限回环实验环境。无担保。