Apache Spark 4.2.0 — высокий уровень — удалённо — запись файла — сессия Connect SET copyFromLocalToFs.allowDestLocal
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-connect-copy-local
Разбор: https://abraxaslabs.tech/research/spark-connect-copy-local
Класс: запись файлов (остаток от SPARK-58531; статичен только на 4.3.0) Доступ: удалённый
Apache Spark 4.2.0 (32f72996) — Apache Software Foundation
spark.sql.artifact.copyFromLocalToFs.allowDestLocal на этой фиксации всё ещё buildConf (настраивается в рамках сессии). SPARK-58531 перенёс его в StaticSQLConf в 4.3.0. Сессия Connect может выполнить SET для тестового шлюза локального назначения, после чего copyFromLocalToFs / AddArtifacts forward_to_fs/<abs> перезаписывает локальный путь драйвера от имени Spark UID.
Сессия Connect выполняет SET, затем forward_to_fs записывает локальные файлы драйвера. Назначение должно быть LocalFileSystem. Это не RCE.
| ID | CVE пока нет (остаток от SPARK-58531) |
| Класс | Запись файлов (остаток от SPARK-58531; статичен только на 4.3.0) |
| Доступ | Удалённый (Spark Connect Config + AddArtifacts) |
| CWE | CWE-22, CWE-732 |
| CVSS | Высокий: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
| Продукт | Apache Spark (ASF) |
| Затронуто | Apache Spark 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8). В 4.3.0 статичен. |
| Аутентификация | Клиент Connect, способный выполнять ConfigRequest.SET и AddArtifacts. По умолчанию Connect не использует PSK. |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | только 127.0.0.1:18920 |
Клиент Spark Connect, имеющий доступ к gRPC (порт привязки по умолчанию 15002, без PSK, если оператор не задал spark.connect.authenticate.token), может включить тестовый шлюз, с которым был запущен драйвер, а затем перезаписать выбранный локальный путь на драйвере от имени Spark UID.
| Метод | Точка записи | Привилегия |
|---|---|---|
gRPC Config SET spark.sql.artifact.copyFromLocalToFs.allowDestLocal=true (или SQL SET) | SparkConnectConfigHandler.handleSet -> RuntimeConfig.set -> SQLConf.setConfString | Целостность (конфигурация сессии, подтверждено в лаборатории): переключает шлюз локального назначения для сессии. Сам по себе файл не записывает. Не изменяет другие сессии. Лаборатория: SET HTTP 200, modifiable=True. |
gRPC AddArtifacts с именем forward_to_fs + абсолютное назначение, CRC в порядке | ArtifactManager.uploadArtifactToFs -> LocalFileSystem.copyFromLocalFile(delSrc=false, overwrite=true) | Целостность (перезапись файла, подтверждено в лаборатории): запись/перезапись локального пути драйвера от имени Spark UID вне artifactRootDirectory/<sessionUUID>/. Лаборатория: назначение /oracle/SPARK-CONNECT-COPY-LOCAL-WITNESS, uid 185, содержимое WITNESS. |
| Тот же AddArtifacts с назначением HDFS/S3/ABFS | документированный copyFromLocalToFs | Целостность (кластерная ФС, по замыслу): не относится к этому остатку. Назначение, не являющееся LocalFileSystem, никогда не обращается к тестовому флагу. |
Подтверждено в лаборатории vs выведено логически: SET ключа buildConf в сессии, затем запись в назначение LocalFileSystem от имени uid 185 вне песочницы артефактов — подтверждено в лаборатории. Перезапись от имени Spark UID пути, доступного процессу на запись (рабочий каталог драйвера, каталог с правами записи для всех, уже исполняемый скрипт, который оператор позже запустит), — выведено логически. Контроля над RIP нет. Не преувеличивайте запись файлов до RCE.
Требуется: Spark Connect слушает. Клиент, который может выполнять SET session SQL conf и AddArtifacts. Dest должен быть Hadoop LocalFileSystem. В Connect по умолчанию нет PSK; если оператор установил токен, остаток всё равно требует этот общий Bearer. Это обход контроля local-dest, а не обход PSK.
| Поверхность | Лаборатория |
|---|---|
| Копирование без SET | 400 _LEGACY_ERROR_TEMP_3161, dest отсутствует |
SET старого статического spark.connect.copyFromLocalToFs.allowDestLocal | 400 CANNOT_MODIFY_STATIC_CONFIG |
Имя артефакта .. / абсолютный staging (ArtifactUtils.concatenatePaths) | INVALID_ARTIFACT_PATH |
Артефакты не-forward_to_fs | остаются под artifactRootDirectory/<sessionUUID>/ |
| HDFS/облачный dest без флага | документированный API, а не этот остаток |
Unauth SQL / Python UDF / Scala UdfPacket на открытом Connect | отправка задания по FAQ вендора; DROP |
| SPARK-52122 ML DefaultParamsReader | allowlist + safeMLClassLoader на этом пине |
Без shell. Без heap dump. Без пропуска Connect PSK. Dest всё ещё должен быть LocalFileSystem. Запись выполняется от имени Spark UID, а не root.
uploadArtifactToFs отказывает в dest LocalFileSystem, если тестовый флаг не включён, с явным комментарием, что иначе пользователь перезаписывает произвольный файл на драйвере:
if (fs.isInstanceOf[LocalFileSystem]) {
val allowDestLocalConf =
session.sessionState.conf.getConf(SQLConf.ARTIFACT_COPY_FROM_LOCAL_TO_FS_ALLOW_DEST_LOCAL)
.getOrElse(
session.conf.get("spark.connect.copyFromLocalToFs.allowDestLocal").contains("true"))
if (!allowDestLocalConf) {
throw new SparkUnsupportedOperationException("_LEGACY_ERROR_TEMP_3161")
}
}
fs.copyFromLocalFile(false, true, new FSPath(localPath.toString), destFSPath)
Ключ 3.5 является статическим, по умолчанию false. Замена в 4.0 — необязательный buildConf:
val ARTIFACT_COPY_FROM_LOCAL_TO_FS_ALLOW_DEST_LOCAL =
buildConf("spark.sql.artifact.copyFromLocalToFs.allowDestLocal")
.internal()
...
.booleanConf
.createOptional
buildConf не вызывает registerStaticConfigKey. isModifiable — это containsConfigKey && !isStaticConfigKey. RuntimeConfig.set отклоняет только статические ключи. Internal по-прежнему можно задать. После SET getConf(ARTIFACT_...) возвращает Some(true), и запасной вариант getOrElse к статической старой конфигурации пропускается.
SPARK-58531 / PR 57730 "Convert spark.sql.artifact.copyFromLocalToFs.allowDestLocal to a static conf" закрыт 2026-08-05, Fix Version 4.3.0. JIRA: конфигурация "controls whether a caller can write to an arbitrary path on the driver's local filesystem, so it is meant to be set by whoever starts the driver." В Affects указана версия 4.2.0. В этом пине всё ещё используется runtime-конфигурация.