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 会从查找 worker 的 sys.path 自动注册 pyspark_* Python 数据源。Connect 会话隔离将工件保留在 SparkFiles/<sessionUUID>/ 下,而每会话的 registerPython 仍留在 runtimeDataSourceBuilders 上。首次查找仍会将触发会话的 SparkFiles 目录放入 sys.path,pickle 每个 DefaultSource,并将这些类缓存到伴生对象中。之后的 Connect SQL(或 Hive Thrift SQL)read.format(name) / USING name 会将该类作为 Spark UID 运行,而无需发送 Python proto。
植入者是已获准使用 Python 的 Connect Python 租户。受害者是同一 Connect driver 上仅使用 SQL 的租户。Java 别名(parquet、csv、json、orc、jdbc)仍走 Java ServiceLoader 路径。映射填满后的后续植入不会刷新。AC:H 是首次查找加上一个非 Java 别名的名称。
| ID | 暂无 CVE |
| 类别 | 权限残留(跨会话 Python 数据源以 Spark UID 身份运行;并非未认证作业提交) |
| 可达性 | 远程(共享 Spark Connect driver) |
| 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 数据源静态注册表 |
| 认证 | Connect Python 租户(植入者)加上之后的仅 SQL Connect / Hive Thrift SQL 用户(受害者) |
| 许可证 | GNU Affero GPL v3.0 |
| 实验环境 | 仅 127.0.0.1:18970。见证文件。伴生映射的 gadget。 |
在共享 driver 上持有一个 Spark Connect Python 会话。植入 pyfiles/pyspark_<name>.py,其 DefaultSource.name() 是一个不存在的连接器短名称(delta、iceberg、hudi……)。在该会话上触发首次 Python 数据源查找。同一 JVM 中之后每个执行 read.format(name) 或 SQL USING name 的会话,都会在 driver planner worker 和 executor 上将该 pickle 类作为 Spark UID 实例化。
这就是完整的权限集合。植入者本就在运行 Python。残留之处在于:一个从未发送 REGISTER_DATA_SOURCE / Python UDF proto 的仅 SQL 租户,仍会执行该类。
| 方法 | 汇聚点 | 权限 |
|---|---|---|
植入者:AddArtifacts 名称 pyfiles/pyspark_<name>.py,然后首次 format(name) / 查找 | 伴生 DataSourceManager.dataSourceBuilders(DataSourceManager.scala object)加上 lookup_data_sources.py 对 pyspark_* 的 iter_modules | 将触发会话的 pickle 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。Oracle 文件包含 SPARK-PYTHON-DS-STATIC-WITNESS。 |
| 方法 | 汇聚点 | 结果 |
|---|---|---|
| 映射填满后植入第二个名称 | 同一静态映射 | 已关闭。先到先得。实验环境:later-lookup found=False,伴生仍只有 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 | driver + executor Python worker | 以 Spark UID 身份在集群范围执行代码。实验环境写入见证文件而非 payload。 |
| 同一路径 | worker 崩溃 / 空转 | 以 Spark UID 身份影响可用性。并非 High。High 是代码执行。 |
共享 SparkSession 上的 Hive Thrift SQL USING | 同一静态映射 | 若 Thrift 与 Connect 共享 driver JVM,则存在相同残留。未实际验证。 |
parquet / csv / json / orc / jdbc 劫持。ServiceLoader 胜出。concatenatePaths 拒绝 .. 和绝对名称)。pythonExec。Connect planner 使用服务端 PYSPARK_PYTHON / python3。registerPython 的跨会话泄漏。Runtime 映射按 DataSourceManager 实例隔离。pyspark 单会话。不存在其他租户。我固定了 apache/spark v4.2.0(32f7299601108917fb01920a54e084595b7b3bf8)。二十八次源码搜寻。作业提交执行用户代码是设计使然。History Jackson、UI shell、PySpark 本地模拟以及 standalone RPC 接收路径在此次剥离中均已关闭。Connect 工件将 pyfiles 隔离在会话 UUID 下。Runtime registerPython 按会话隔离。伴生对象则不然。
SPARK-45917 从 PYTHONPATH 自动注册 pyspark_* 包。这是为运维人员安装的连接器而设计的。残留之处在于:查找 worker 还会 sys.path.insert 触发会话的 SparkFiles 目录(worker_util.setup_spark_files),然后将找到的任何 pyspark_* DefaultSource pickle 到一个永不刷新的进程全局映射中。
错误方向:将 registerPython 隔离视为整个租户屏障。Runtime 是隔离的。Static 则不然。
cd lab
./run.sh
回环 127.0.0.1:18970。Compose 项目 spark-python-ds-static。见证 SPARK-PYTHON-DS-STATIC-WITNESS。object DataSourceManager 的 Python gadget 加上每会话 runtime 映射。仅在攻击者 SparkFiles 中植入 pyspark_delta.py。攻击者查找填充伴生。之后植入 iceberg 不会刷新。受害者 read delta 写入 oracle。registerPython("alpha") 不会泄漏。Java parquet 保持 Java。
最后一行:
SUCCESS SPARK-PYTHON-DS-STATIC static-cross-session=yes runtime-isolated=yes first-wins=yes java-alias-closed=yes SPARK-PYTHON-DS-STATIC-WITNESS
在填充静态映射时,不要将会话 SparkFiles 放入查找 worker 的 sys.path。仅加载运维人员 PYTHONPATH 包(SPARK-45917 的设计意图)。或者像工件已经做的那样,按会话 / JobArtifactSet UUID 为静态映射设置键。先到先得的空填充不应在 driver 的整个生命周期内固定另一个租户的类。
在此之前,不要在 Python 租户与仅 SQL 租户之间共享 Connect driver——除非你本就信任后者拥有 driver Python 权限。
32f7299601108917fb01920a54e084595b7b3bf8)DataSourceManager.scala 伴生 dataSourceBuilders / initialStaticDataSourceBuilderslookup_data_sources.py pyspark_* DefaultSourceworker_util.py setup_spark_filesPythonDataSourceV2.scalaAbraxas Labs - abraxaslabs.tech - github.com/abraxas - @abraxas_null - [email protected]