
Apache Spark 4.2.0 - 高 - リモート - ファイル読み取り - History zip が仕込まれたシンボリックリンクを追跡
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 は依然としてすべての listStatus の子を FileSystem.open 経由でダンプする。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 | High: 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 が confused deputy となる。
| 権限 | メソッド / シンク | ラボ実証済みまたは推測 |
|---|---|---|
| 機密性: SHS UID が開けるホストファイルの読み取り | 有効な appstatus_* と events_* JSON (SparkListenerLogStart / SparkListenerApplicationStart) を含む eventlog_v2_<id>/ を仕込み、さらにログディレクトリ外をターゲットとするシンボリックリンクの子を配置する。スキャンを待つ。認証不要の 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 メンバーには witness がない。エントリ名はターゲットパスではなく、シンボリックリンクのベース名のままである。 |
機密性: 攻撃者が open できない (660) 兄弟テナントのイベントログ | 同じ zip、シンボリックリンクのターゲットは別アプリのログ | 推測。同じ FileSystem.open。未検証。 |
機密性: keytab、spark-env.sh、SHS が読める場合は /etc/passwd | 同じ zip | 推測。オラクルは専用の /secret/host-secret であり、これらのファイルではない。 |
| SHS またはホストファイルの完全性 | zip は HTTP ボディに読み込まれる | 閉じている。ラボ実証で影響なし。 |
| SHS またはホストファイルの可用性 | zip はターゲットを削除または切り詰めない | 閉じている。ラボ実証で影響なし。 |
| ホスト RCE / Jackson ガジェット構築 | 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 はリンクを unlink するのであり、ターゲットではない。 |
| 認証不要のジョブ投入 / ML モデルロード | SECURITY.md FAQ / docs/ml-security.md | 閉じている。設計上、この残存物ではない。 |
master や 4.3 RC ではなく、apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) に固定した。28 回の調査。Jackson History RCE はこのピールでは閉じている (JsonProtocol.scala:965-972)。History Server は依然として攻撃者が書き込めるログディレクトリを指しており、これは CVE-2025-54920 と同じ被害者モデルである。
zipEventLogFiles はイベントファイルだけでなく、すべての files を zip 化する:
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 の話のすべてとして扱うこと、witness を events_* に仕込むこと (ラボはこれを失敗させる)、zip エントリがターゲットパスを運ぶと期待すること、resolveLogPath のプレフィックス結合をエスケープとみなすこと、このピンで HiveConnection Event クラスを試すこと。
python3 spark-history-zip-symlink-Abraxas-Labs.py
または
cd lab
./run.sh
EventLogFileReaders.scala のローリング zip ダンプのガジェットであり、完全な History Server でも公式の spark:4.2.0 イメージでもない。Event JSON を Jackson 経由でパースしない。ループバック 127.0.0.1:18980。Compose プロジェクト spark-history-zip-symlink。ローリングディレクトリは有効な appstatus_* + events_* (witness なし) と、planted-secret -> /secret/host-secret を保持する。
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
すべての listStatus 名ではなく、events_* / appstatus_* の子のみをパックすること。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 を参照。ループバックラボのみ。無保証。