Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2025-60012-POC — A POC for the Apache Livy Unauthorized File Access Vunerability | Kitploit
Tools/GitHubGitHub/sid6224/cve-2025-60012-poc
Vulnerability AnalysisExploitationWeb Application ExploitationCloud SecurityLearning & EducationLabs & Practice
GitHubsid6224/cve-2025-60012-poc

CVE-2025-60012-POC

A POC for the Apache Livy Unauthorized File Access Vunerability

View Repository
56 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

For educational and security research purposes only. Do not use against systems you do not own or have explicit written permission to test. → Full Disclaimer

CVE-2025-60012 — Apache Livy Unauthorized File Access

CVE Livy Severity CWE Type License Platform Language

Overview

FieldDetail
CVE IDCVE-2025-60012
SeverityMedium (CVSS 6.3)
AffectedApache Livy 0.7.0-incubating, 0.8.0-incubating — when connected to Apache Spark 3.1 or later
Fixed inApache Livy 0.9.0-incubating
CWECWE-20: Improper Input Validation
Disclosed2026-03-13
ReporterFurue Hideyuki

Vulnerability Description

An authenticated user with access to Livy's REST or JDBC interface can submit a Spark session or batch job with crafted configuration values. Two weaknesses combine to allow the attacker to reference local filesystem files outside their permitted paths:

  1. Missing validation for spark.archives — Spark 3.1 introduced spark.archives as a unified way to distribute archive files across all cluster managers. Livy 0.8.0's hardcoded list of configuration keys that get path-validated (HARDCODED_SPARK_FILE_LISTS) does not include spark.archives. A path passed via this key is therefore never checked against the local filesystem whitelist (livy.file.local-dir-whitelist), allowing an attacker to reference any local file.

  2. Path traversal bypass in whitelist check — Even for configuration keys that ARE validated, the whitelist comparison in Livy 0.8.0 uses a plain Java String startsWith call on the raw path. An attacker can bypass this using path traversal: /whitelisted/dir/../../etc/passwd passes the string check but resolves outside the allowed directory.

Affected Source Files

File 1 — LivyConf.scala

Vulnerable (v0.8.0): https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala

Fixed (v0.9.0): https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/LivyConf.scala

File 2 — Session.scala

Vulnerable (v0.8.0): https://github.com/apache/incubator-livy/blob/v0.8.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala

Fixed (v0.9.0): https://github.com/apache/incubator-livy/blob/v0.9.0-incubating/server/src/main/scala/org/apache/livy/sessions/Session.scala

Source Code — Clone Commands

Both versions were cloned directly from the official Apache Livy GitHub repository into this workspace using the following exact commands:

Repository: https://github.com/apache/incubator-livy

# Vulnerable version — cloned into ./livy-0.8.0/
git clone --depth=1 --branch v0.8.0-incubating \
  https://github.com/apache/incubator-livy \
  livy-0.8.0

# Fixed version — cloned into ./livy-0.9.0/
git clone --depth=1 --branch v0.9.0-incubating \
  https://github.com/apache/incubator-livy \
  livy-0.9.0
VersionTagResolved commitLocal path
0.8.0-incubatingv0.8.0-incubating78b512658e4baf1183f2b352203ada1928d8111a./livy-0.8.0/
0.9.0-incubatingv0.9.0-incubating7215f209b25b96488189567807eaded00953a492./livy-0.9.0/

Exact Code Diffs

Diffs were produced by cloning both tags locally (see above) and running:

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

Fix 1 — LivyConf.scala: spark.archives added to hardcoded file list

   private val HARDCODED_SPARK_FILE_LISTS = Seq(
     SPARK_JARS,
     SPARK_FILES,
     SPARK_ARCHIVES,
     SPARK_PY_FILES,
+    "spark.archives",        // <-- ADDED in v0.9.0 (Spark 3.1+ config key)
     "spark.yarn.archive",
     "spark.yarn.dist.files",
     "spark.yarn.dist.jars",
     "spark.yarn.jar",
     "spark.yarn.jars"
   )

Impact of missing entry in v0.8.0: When a user submits a session with conf: {"spark.archives": "file:///etc/passwd"}, Livy 0.8.0 never calls resolveURIs() on that value and never checks it against livy.file.local-dir-whitelist. The path is forwarded to Spark unvalidated.


Fix 2 — Session.scala: Path normalisation before whitelist check

   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.")
     }
   }

Impact in v0.8.0: The raw string startsWith check can be bypassed with a path traversal payload.

Example: if 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 (bypassed)
  • v0.9.0: Paths.get("/opt/safe-data/../../../etc/passwd").normalize → /etc/passwd /etc/passwd.startsWith(/opt/safe-data) → false (blocked)

Attack Vector Summary

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

Test Environment

All steps in this PoC were executed and validated on the following system:

ComponentDetail
Host OSUbuntu 24.04.4 LTS (Noble Numbat)
Kernel6.17.0-14-generic x86_64
Architecturex86_64
Total Memory15.49 GiB
Docker Engine28.2.2
Host JDKOpenJDK 17.0.18 (used by host only — containers use eclipse-temurin:11-jdk-focal)
Container base imageeclipse-temurin:11-jdk-focal (JDK 11, Ubuntu Focal)
Spark version (both images)3.1.3 with Hadoop 3.2
Livy version — vulnerable image0.8.0-incubating (Scala 2.12 build)
Livy version — fixed image0.9.0-incubating (Scala 2.12 build)

Repository Structure

.
├── 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

Proof of Concept

Overview

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

Full end-to-end sequence — follow Steps 1 through 4 in order:

Download Tool