
Ein POC für Apache Livy Path Traversal Whitelist Bypass Vulnerability
Nur für Bildungs- und Sicherheitsforschungszwecke. Nicht gegen Systeme verwenden, die Sie nicht besitzen oder für die Sie keine ausdrückliche schriftliche Erlaubnis zum Testen haben. → Vollständiger Haftungsausschluss
| Feld | Detail |
|---|---|
| CVE ID | CVE-2025-66249 |
| Schweregrad | Wichtig (CVSS N/A — NVD-Bewertung ausstehend, Stand 15.03.2026) |
| Betroffen | Apache Livy 0.3.0-incubating bis 0.8.0-incubating — nur wenn livy.file.local-dir-whitelist auf einen nicht standardmäßigen Wert gesetzt ist |
| Behoben in | Apache Livy 0.9.0-incubating |
| CWE | CWE-22: Unzureichende Einschränkung eines Pfadnamens auf ein eingeschränktes Verzeichnis ('Path Traversal') |
| Gemeldet | 2026-03-12 (OSS-Sec) / 2026-03-13 (NVD) |
| Melder | Hiroki Egawa (Entdecker) |
Ein authentifizierter Benutzer mit Zugriff auf die REST- oder JDBC-Schnittstelle von Livy kann eine Spark-Sitzung oder einen Stapelauftrag mit einem manipulierten Dateipfad-Konfigurationswert einreichen, der die zulässige Verzeichnis-Whitelist umgeht.
Grundursache — Path-Traversal-Umgehung in der Whitelist-Prüfung (Session.scala)
Wenn livy.file.local-dir-whitelist konfiguriert ist, validiert Livy 0.8.0 eingereichte Pfade durch Aufruf von Java's String.startsWith() auf den rohen, nicht normalisierten Pfad. Diese Prüfung kann mit ../-Traversierungssequenzen umgangen werden:
/opt/safe-data/../sensitive/secret.txt
Der rohe String beginnt mit /opt/safe-data, daher besteht die Prüfung — aber der Pfad löst zu /opt/sensitive/secret.txt auf, der sich vollständig außerhalb des whitelistierten Verzeichnisses befindet.
Auslösebedingung: Die Schwachstelle kann nur ausgenutzt werden, wenn livy.file.local-dir-whitelist auf einen nicht standardmäßigen (nicht leeren) Wert gesetzt ist. Wenn die Whitelist leer ist (Standard), wird die Pfadvalidierung vollständig übersprungen und das Problem tritt nicht auf.
Auswirkung: Ein Angreifer, der eine Sitzung über die Livy-REST-API einreicht, kann auf beliebige lokale Dateien auf dem Livy-Server-Host zugreifen. In einem gemeinsam genutzten Analyse-Cluster führt dies potenziell zur Offenlegung von Anmeldeinformationen, Schlüsseln, Konfigurationsdateien oder anderen Daten, die für den Benutzer des Livy-Prozesses lesbar sind.
Session.scalaVerwundbar (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 geklont:
Repository: https://github.com/apache/incubator-livy
# Verwundbare Version — geklont in ./livy-0.8.0/
git clone --depth=1 --branch v0.8.0-incubating \
https://github.com/apache/incubator-livy \
livy-0.8.0
# Behobene Version — geklont in ./livy-0.9.0/
git clone --depth=1 --branch v0.9.0-incubating \
https://github.com/apache/incubator-livy \
livy-0.9.0
| Version | Tag | Aufgelöster Commit | Lokaler Pfad |
|---|---|---|---|
| 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/ |
Session.scala: Paths.get().normalize() vor Whitelist-Prüfung import java.io.InputStream
import java.net.{URI, URISyntaxException}
+import java.nio.file.Paths
import java.security.PrivilegedExceptionAction
+import java.util.concurrent.{Executors, LinkedBlockingQueue, ThreadFactory, ThreadPoolExecutor, TimeUnit}
import java.util.UUID
...
if (resolved.getScheme() == "file") {
// Make sure the location is whitelisted before allowing local files to be added.
- 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:
Die rohe startsWith-Prüfung kann mit einem Path-Traversal-Payload umgangen werden.
Beispiel: Wenn livy.file.local-dir-whitelist = /opt/safe-data
/opt/safe-data/../sensitive/secret.txt
"/opt/safe-data/../sensitive/secret.txt".startsWith("/opt/safe-data") → true (umgangen)Paths.get("/opt/safe-data/../sensitive/secret.txt").normalize → /opt/sensitive/secret.txt
/opt/sensitive/secret.txt.startsWith(/opt/safe-data) → false (blockiert)Die Unterschiede wurden durch lokales Klonen beider Tags (siehe oben) und Ausführen von:
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
Attacker (authenticated REST/JDBC user)
│
▼
POST /sessions
{
"conf": {
"spark.jars": "file:///opt/safe-data/../sensitive/secret.txt"
← path starts with whitelisted prefix — String.startsWith() passes
← but resolves OUTSIDE the directory via ../ traversal
}
}
│
▼
Livy 0.8.0 — whitelist check bypassed (raw startsWith, no normalisation)
│
▼
Spark reads the file and distributes it to executors
│
▼
Attacker retrieves file contents via job output / logs
Alle Schritte in diesem PoC wurden auf dem folgenden System ausgeführt und validiert:
| Komponente | Detail |
|---|---|
| Host-Betriebssystem | Ubuntu 24.04.4 LTS (Noble Numbat) |
| Kernel | 6.17.0-14-generic x86_64 |
| Architektur | x86_64 |
| Gesamtspeicher | 15 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 — verwundbares Image | 0.8.0-incubating |
| Livy-Version — behobenes Image | 0.9.0-incubating |
CVE-2025-66249-POC/
├── docker/
│ ├── fixed/
│ │ ├── Dockerfile
│ │ ├── livy.conf
│ │ └── start.sh
│ └── vulnerable/
│ ├── Dockerfile
│ ├── livy.conf
│ └── start.sh
├── test/
│ └── validate.sh
├── .gitignore
├── LICENSE
└── README.md