
Apache Spark 4.2.0 — высокий уровень — удалённо — остаточные привилегии — глобальная для процесса карта Python Data Source
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-python-ds-static
Класс: Остаточные привилегии Достижимость: Удалённая
Apache Spark 4.2.0 (32f72996) — Apache Software Foundation
DataSourceManager.dataSourceBuilders — это JVM-глобальная карта с правилом «первый побеждает». SPARK-45917 автоматически регистрирует Python Data Sources pyspark_* из sys.path воркера поиска. Изоляция сессий Connect сохраняет артефакты в SparkFiles/<sessionUUID>/, а registerPython для каждой сессии остаётся в runtimeDataSourceBuilders. Первый поиск всё равно помещает каталог SparkFiles вызвавшей его сессии в sys.path, сериализует каждый DefaultSource и кэширует эти классы в companion-объекте. Последующий Connect SQL (или Hive Thrift SQL) read.format(name) / USING name запускает этот класс как Spark UID без отправки Python-прототипа.
Злоумышленник — это Python-арендатор Connect, которому Python уже разрешён. Жертва — арендатор, работающий только с SQL, на том же драйвере Connect. Java-алиасы (parquet, csv, json, orc, jdbc) остаются на пути Java ServiceLoader. Последующие внедрения после заполнения карты не обновляют её. AC:H — это первый поиск плюс имя, не являющееся Java-алиасом.
| ID | CVE пока нет |
| Класс | Остаточные привилегии (кросс-сессионный Python Data Source как Spark UID; не отправка заданий без аутентификации) |
| Достижимость | Удалённая (общий драйвер Spark Connect) |
| CWE | CWE-269, CWE-502 |
| CVSS | Высокий: 8.8 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Продукт | Apache Spark |
| Затронуто | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) статический реестр Python Data Source |
| Аутентификация | Python-арендатор Connect (злоумышленник) плюс позднее пользователь Connect / Hive Thrift SQL только с SQL (жертва) |
| Лицензия | GNU Affero GPL v3.0 |
| Лаборатория | Только 127.0.0.1:18970. Файл-свидетель. Гаджет companion-карты. |
Удерживать Python-сессию Spark Connect на общем драйвере. Внедрить pyfiles/pyspark_<name>.py, чей DefaultSource.name() — это отсутствующее короткое имя коннектора (delta, iceberg, hudi, ...). Запустить первый поиск Python Data Source в этой сессии. Любая последующая сессия в той же JVM, которая через read.format(name) или SQL USING name создаёт экземпляр сериализованного класса как Spark UID на воркере планировщика драйвера и на исполнителях.
Это и есть полный набор привилегий. Злоумышленник уже выполняет Python. Остаточность в том, что арендатор, работающий только с SQL, который никогда не отправлял REGISTER_DATA_SOURCE / Python UDF-прототип, всё равно выполняет этот класс.
| Метод | Точка сброса | Привилегия |
|---|---|---|
Злоумышленник: AddArtifacts с именем pyfiles/pyspark_<name>.py, затем первый format(name) / поиск | companion DataSourceManager.dataSourceBuilders (объект DataSourceManager.scala) плюс lookup_data_sources.py iter_modules для pyspark_* | Кэшировать сериализованный DefaultSource вызвавшей сессии в масштабе JVM. Лаборатория: layer='static' trigger='attacker' first-wins=yes. |
Жертва: SQL USING <name> / spark.read.format(name) в сессии, которая ничего не внедряла | PythonDataSourceV2 createDataSourceInPython | Запустить этот класс как Spark UID. Лаборатория: planted_by='attacker' witness_written=True static-cross-session=yes. Файл-оракул содержит SPARK-PYTHON-DS-STATIC-WITNESS. |
| Метод | Точка сброса | Результат |
|---|---|---|
| Последующее внедрение второго имени после заполнения карты | та же статическая карта | Закрыто. Первый побеждает. Лаборатория: later-lookup found=False, companion по-прежнему только delta. |
Посессионный registerPython("alpha") | runtimeDataSourceBuilders | Изолировано. Клон копирует только runtime. Лаборатория: runtime-isolated=yes. Чтение alpha жертвой даёт found=False. |
Java-алиас parquet | Java DataSourceRegister ServiceLoader | Закрыто. Лаборатория: java-alias-closed=yes layer='java'. То же для csv/json/orc/jdbc. |
| Метод | Точка сброса | Привилегия |
|---|---|---|
USING жертвы после внедрения | Python-воркеры драйвера и исполнителей | Код в масштабе кластера как Spark UID. Лаборатория записывает файл-свидетель вместо полезной нагрузки. |
| Тот же путь | падение / зависание воркера | Доступность как Spark UID. Не Высокий. Высокий — это выполнение кода. |
Hive Thrift SQL USING на общем SparkSession | та же статическая карта | Та же остаточность, если Thrift и Connect используют одну JVM драйвера. Не проверялось. |
parquet / csv / json / orc / jdbc. Побеждает ServiceLoader.concatenatePaths отклоняет .. и абсолютные имена).pythonExec, предоставленный клиентом. Планировщик Connect использует серверные PYSPARK_PYTHON / python3.registerPython. Runtime-карта привязана к экземпляру DataSourceManager.pyspark для одного пользователя с одной сессией. Другого арендатора нет.Я зафиксировал apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8). Двадцать восемь поисков по исходникам. Отправка заданий, выполняющая пользовательский код, — по замыслу. History Jackson, оболочка UI, локальная имперсонация PySpark и путь приёма standalone RPC закрыты на этом этапе. Артефакты Connect изолируют pyfiles под UUID сессии. Runtime registerPython привязан к сессии. Companion-объект — нет.
SPARK-45917 автоматически регистрирует пакеты pyspark_* из PYTHONPATH. Это предназначено для коннекторов, установленных оператором. Остаточность в том, что воркер поиска также выполняет sys.path.insert для каталога SparkFiles вызвавшей сессии (worker_util.setup_spark_files), а затем сериализует любой найденный pyspark_* DefaultSource в процессно-глобальную карту, которая никогда не обновляется.
Неверный поворот: считать изоляцию registerPython всем барьером между арендаторами. Runtime изолирован. Статическая часть — нет.
cd lab
./run.sh
Loopback 127.0.0.1:18970. Проект Compose spark-python-ds-static. Свидетель SPARK-PYTHON-DS-STATIC-WITNESS. Python-гаджет object DataSourceManager плюс посессионные runtime-карты. Внедрить pyspark_delta.py только в SparkFiles атакующего. Поиск атакующего заполняет companion. Последующее внедрение iceberg не обновляет её. Чтение delta жертвой записывает оракул. registerPython("alpha") не утекает. Java parquet остаётся Java.
Последняя строка: