Apache Spark 4.2.0 — высокий уровень — удалённо — запись файла — History appStorePath несанитизированный App ID
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-store-path
Класс: запись файла (ограничено конфигурацией) Достижимость: удалённая
Apache Spark 4.2.0 (32f72996) — Apache Software Foundation
HistoryServerDiskManager.appStorePath выполняет new File(appStoreDir, appId + extension) без санитизации. Utils.sanitizeDirName предназначен только для имён файлов журналов событий. JSON App ID берётся через asText(). Листинг сохраняет event.appId.orNull. Lease.commit использует эту строку. App ID ../ записывает .rdb за пределами spark.history.store.path. По умолчанию неустановленный store.path работает в памяти (без записи). Содержимое — это файл хранилища, а не RCE.
| ID | CVE пока нет |
| Класс | Запись файла (дисковое хранилище History, ограничено конфигурацией) |
| Достижимость | Удалённая (запись журнала событий плюс History с установленным spark.history.store.path; ACL интерфейса по умолчанию отключены) |
| CWE | CWE-22 |
| CVSS | Высокий: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N при установленном spark.history.store.path |
| Продукт | Apache Spark History Server |
| Затронуто | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| Аутентификация | писатель в spark.history.fs.logDirectory (по умолчанию file:/tmp/spark-events); у History должен быть установлен spark.history.store.path |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1:18990. Гаджет из указанного соединения. Свидетель .rdb за пределами store.path. |
Та же модель жертвы, что и в CVE-2025-54920: History Server направлен на каталог журналов, в который атакующий может писать. Jackson больше не конструирует гаджеты класса HiveConnection. Путь дискового хранилища по-прежнему доверяет App ID из JSON события.
| Метод | Точка сброса | Привилегия | Лаборатория против вывода |
|---|---|---|---|
Подложить журнал событий, в JSON которого App ID равен ../<name> (или ../../oracle/...). Дождаться сканирования. GET, который загружает интерфейс приложения / getAppUI / loadDiskStore / createDiskStore | HistoryServerDiskManager.appStorePath, затем Lease.commit renameTo | Целостность: создать путь .rdb (по умолчанию RocksDB) или .ldb (LevelDB) за пределами {store.path}/apps от имени UID History. Доказано в лаборатории на гаджете: escaped=yes, файл /work/oracle/SPARK-HISTORY-STORE-PATH-WITNESS.rdb содержит свидетеля. Содержимое — это файл хранилища, а не выбранный атакующим байт-код хоста и не оболочка. | Доказано в лаборатории (гаджет записывает байты свидетеля по вырвавшемуся пути). Полный renameTo каталога RocksDB в History — это то же соединение. |
| Тот же App ID, когда вырвавшийся пункт назначения уже является каталогом | Lease.commit if (dst.isDirectory()) deleteStore(dst), затем Utils.deleteRecursively | Доступность: удалить существующий каталог, имя которого уже заканчивается на суффикс бэкенда, затем заменить его арендованным хранилищем. | Выведено (фиксация HistoryServerDiskManager.scala). Гаджет записал файл, но не вызвал deleteRecursively. |
По умолчанию неустановленный spark.history.store.path | LOCAL_STORE_DIR createOptional; diskManager = storePath.map | Закрыто. Листинг в памяти. Нет HistoryServerDiskManager. Доказано в лаборатории gated=yes written=no. | Доказано в лаборатории |
Применить Utils.sanitizeDirName к App ID заранее | вспомогательная функция для имени файла журнала событий | Остаётся внутри apps/. / и : превращаются в -. Доказано в лаборатории как отрицательный результат. | Доказано в лаборатории |
Контролируемый App ID local-control при установленном store.path | то же соединение | Записывает {store.path}/apps/local-control.rdb. Остаётся внутри apps/. | Доказано в лаборатории |
Смежное закрыто: хранилище по умолчанию не установлено (в памяти). Jackson sparkEventFromJson по-прежнему требует SparkListenerEvent (CVE-2025-54920). Резервный вариант URL getAppUI eventlog_v2_ + nameForAppAndAttempt санитизируется. Ключ KV листинга не является путём файловой системы. У маппера HybridStore / kvstore нет Id.CLASS. History HTTP не принимает путь загрузки. Компакция по умолчанию отключена. Очистка по умолчанию отключена. Отправка заданий в открытом кластере (SECURITY.md FAQ). Загрузка ML-модели (docs/ml-security.md). Этот остаток — не RCE хоста.
Я зафиксировал Spark 4.2.0, не master и не 4.3 RC. Двадцать восемь поисков. Публичный RCE через Jackson в History закрыт на этом срезе. Писатели журналов событий уже вызывают Utils.sanitizeDirName, поэтому файл журнала не может содержать /. Имя файла дискового кэша использует необработанный App ID.
private[history] def appStorePath(appId: String, attemptId: Option[String]): File = {
val fileName = appId + attemptId.map("_" + _).getOrElse("") + extension
new File(appStoreDir, fileName)
}
appStoreDir — это {spark.history.store.path}/apps. В Unix new File(parent, "../../oracle/name.rdb") разрешается за пределами parent. JsonProtocol.applicationStartFromJson берёт json.get("App ID").asText(). AppListingListener.onApplicationStart сохраняет event.appId.orNull. lease.commit(appId, attempt.info.attemptId) передаёт эту строку в appStorePath.
Уже зафиксированные неверные повороты: трактовка неустановленного по умолчанию store.path как записи (он в памяти); ожидание, что Unix new File(dir, "/abs") проигнорирует parent (это не так; остаток — ..); утверждение об RCE из-за суффикса .rdb; запуск полного History Server, когда соединение и есть оракул; трактовка sanitizeDirName как покрывающего этот путь.
python3 spark-history-store-path-Abraxas-Labs.py
или
cd lab
./run.sh
Гаджет HistoryServerDiskManager.appStorePath на 127.0.0.1:18990. Фрагменты фиксации в lab/pin/. Полный History Server не запускается. Привяжите его к loopback.
SUCCESS SPARK-HISTORY-STORE-PATH how=gadget escaped=yes gated=yes outside=/work/oracle/SPARK-HISTORY-STORE-PATH-WITNESS.rdb SPARK-HISTORY-STORE-PATH-WITNESS
Выполните Utils.sanitizeDirName (или отклоните .. / / / NUL) для appId и attemptId внутри appStorePath перед new File. После соединения требуйте, чтобы dst.getCanonicalFile() оставался внутри appStoreDir.getCanonicalFile(). Неустановленный spark.history.store.path остаётся безопасным значением по умолчанию.