
Ein POC für die Apache Livy Unauthorized File Access Vunerability
Nur für Bildungs- und Sicherheitsforschungszwecke. Verwenden Sie dies nicht gegen Systeme, die Ihnen nicht gehören oder für die Sie keine ausdrückliche schriftliche Genehmigung zum Testen haben. → Vollständiger Haftungsausschluss
| Feld | Detail |
|---|---|
| CVE ID | CVE-2025-60012 |
| Schweregrad | Mittel (CVSS 6,3) |
| Betroffen | Apache Livy 0.7.0-incubating, 0.8.0-incubating — wenn mit Apache Spark 3.1 oder neuer verbunden |
| Behoben in | Apache Livy 0.9.0-incubating |
| CWE | CWE-20: Unzureichende Eingabevalidierung |
| Offengelegt | 2026-03-13 |
| Melder | Furue Hideyuki |
Ein authentifizierter Benutzer mit Zugriff auf die REST- oder JDBC-Schnittstelle von Livy kann eine Spark-Session oder einen Batch-Job mit manipulierten Konfigurationswerten einreichen. Zwei Schwächen kombinieren sich, um dem Angreifer den Zugriff auf lokale Dateisysteme außerhalb der erlaubten Pfade zu ermöglichen:
Fehlende Validierung für spark.archives — Spark 3.1 führte spark.archives als
einheitliche Möglichkeit ein, Archivdateien über alle Cluster-Manager zu verteilen. Livy 0.8.0s fest programmierte
Liste von Konfigurationsschlüsseln, die pfadgeprüft werden (HARDCODED_SPARK_FILE_LISTS), enthält
spark.archives nicht. Ein über diesen Schlüssel übergebener Pfad wird daher nie gegen die
Whitelist des lokalen Dateisystems (livy.file.local-dir-whitelist) geprüft, wodurch ein Angreifer
auf jede lokale Datei verweisen kann.
Umgehung der Pfadüberprüfung durch Path-Traversal — Selbst bei Konfigurationsschlüsseln, die VALIDIERT werden,
verwendet der Whitelist-Vergleich in Livy 0.8.0 einen einfachen Java-String startsWith-Aufruf
auf den rohen Pfad. Ein Angreifer kann dies durch Path-Traversal umgehen:
/whitelisted/dir/../../etc/passwd besteht die String-Prüfung, löst sich aber außerhalb des
erlaubten Verzeichnisses auf.
LivyConf.scalaAnfällig (v0.8.0): https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala
Behoben (v0.9.0): https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala
Session.scalaAnfällig (v0.8.0): https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala
Behoben (v0.9.0): https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala
Beide Versionen wurden direkt aus dem offiziellen Apache Livy GitHub-Repository mit den folgenden genauen Befehlen in diesen Arbeitsbereich geklont:
Repository: 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
| Version | Tag | Resolved commit | Local path |
|---------|-----|-----------------|------------|
| 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/` |
## Exakte Code-Diffs
Diffs wurden erzeugt, indem beide Tags lokal geklont wurden (siehe oben) und folgender Befehl ausgeführt wurde:```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 zur hartcodierten Dateiliste hinzugefügt```diffprivate val HARDCODED_SPARK_FILE_LISTS = Seq( SPARK_JARS, SPARK_FILES, SPARK_ARCHIVES, SPARK_PY_FILES,
**Auswirkungen des fehlenden Eintrags in v0.8.0:**
Wenn ein Benutzer eine Sitzung mit `conf: {"spark.archives": "file:///etc/passwd"}` einreicht, ruft Livy 0.8.0 niemals `resolveURIs()` für diesen Wert auf und prüft ihn nie gegen `livy.file.local-dir-whitelist`. Der Pfad wird unvalidiert an Spark weitergeleitet.
---
### Fix 2 — `Session.scala`: Pfadnormalisierung vor der Whitelist-Überprüfung```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.")
}
}
Auswirkung in v0.8.0:
Der Rohstring startsWith-Check kann mit einem Pfad-Traversal-Payload umgangen werden.
Beispiel: wenn `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** (umgangen)
- v0.9.0: `Paths.get("/opt/safe-data/../../../etc/passwd").normalize` → `/etc/passwd`
`/etc/passwd`.startsWith(`/opt/safe-data`) → **false** (blockiert)
## Angriffsvektor-Zusammenfassung```
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
| Komponente | Detail |
|---|---|
| Host-Betriebssystem | Ubuntu 24.04.4 LTS (Noble Numbat) |
| Kernel | 6.17.0-14-generic x86_64 |
| Architektur | x86_64 |
| Gesamter Arbeitsspeicher | 15.49 GiB |
| Docker Engine | 28.2.2 |
| Host-JDK | OpenJDK 17.0.18 (wird nur vom Host verwendet – Container verwenden eclipse-temurin:11-jdk-focal) |
| Container-Basisimage | eclipse-temurin:11-jdk-focal (JDK 11, Ubuntu Focal) |
| Spark-Version (beide Images) | 3.1.3 mit Hadoop 3.2 |
| Livy-Version — anfälliges Image | 0.8.0-incubating (Scala 2.12-Build) |
| Livy-Version — gefixtes Image | 0.9.0-incubating (Scala 2.12-Build) |
. ├── 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
## Konzeptnachweis