
Apache Spark 4.2.0 - Alta - Remota - Privilegio residual - mapa global de procesos de Python Data Source
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-python-ds-static
Análisis: https://abraxaslabs.tech/research/spark-python-ds-static
Clase: Privilegio residual Alcance: Remoto
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
DataSourceManager.dataSourceBuilders es un mapa global de la JVM en el que gana el primero. SPARK-45917 registra automáticamente los Python Data Sources pyspark_* desde el sys.path del worker de lookup. El aislamiento de sesión de Connect mantiene los artefactos bajo SparkFiles/<sessionUUID>/, y el registerPython por sesión permanece en runtimeDataSourceBuilders. El primer lookup aun así coloca el directorio SparkFiles de la sesión que lo dispara en sys.path, serializa con pickle cada DefaultSource y almacena en caché esas clases en el objeto companion. Un read.format(name) / USING name posterior de Connect SQL (o Hive Thrift SQL) ejecuta esa clase como el UID de Spark sin enviar proto de Python.
El plantador es un tenant de Connect Python que ya tiene permiso de Python. La víctima es un tenant solo-SQL en el mismo driver de Connect. Los alias de Java (parquet, csv, json, orc, jdbc) permanecen en la ruta del ServiceLoader de Java. Las plantaciones posteriores una vez llenado el mapa no se refrescan. AC:H es el primer lookup más un nombre que no es un alias de Java.
| ID | aún sin CVE |
| Clase | Privilegio residual (Python Data Source entre sesiones como UID de Spark; no es envío de trabajos sin autenticación) |
| Alcance | Remoto (driver de Spark Connect compartido) |
| CWE | CWE-269, CWE-502 |
| CVSS | Alto: 8.8 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Producto | Apache Spark |
| Afectado | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) registro estático de Python Data Source |
| Autenticación | Tenant de Connect Python (plantador) más un usuario posterior solo-SQL de Connect / Hive Thrift SQL (víctima) |
| Licencia | GNU Affero GPL v3.0 |
| Laboratorio | Solo 127.0.0.1:18970. Archivo testigo. Gadget del mapa companion. |
Mantener una sesión de Spark Connect Python en un driver compartido. Plantar pyfiles/pyspark_<name>.py cuyo DefaultSource.name() sea un nombre corto de conector ausente (delta, iceberg, hudi, ...). Disparar el primer lookup de Python Data Source en esa sesión. Cualquier sesión posterior en la misma JVM que ejecute read.format(name) o SQL USING name instancia la clase serializada con pickle como el UID de Spark en el worker del planificador del driver y en los ejecutores.
Ese es el conjunto completo de privilegios. El plantador ya ejecuta Python. Lo residual es que un tenant solo-SQL, que nunca envió proto de REGISTER_DATA_SOURCE / Python UDF, aun así ejecuta esa clase.
| Método | Sumidero | Privilegio |
|---|---|---|
Plantador: AddArtifacts con nombre pyfiles/pyspark_<name>.py y luego el primer format(name) / lookup | DataSourceManager.dataSourceBuilders del companion (objeto DataSourceManager.scala) más iter_modules de pyspark_* en lookup_data_sources.py | Almacenar en caché el DefaultSource serializado con pickle de la sesión que lo dispara, a nivel de toda la JVM. Laboratorio: layer='static' trigger='attacker' first-wins=yes. |
Víctima: SQL USING <name> / spark.read.format(name) en una sesión que nunca plantó | PythonDataSourceV2 createDataSourceInPython | Ejecutar esa clase como UID de Spark. Laboratorio: planted_by='attacker' witness_written=True static-cross-session=yes. El archivo oráculo contiene SPARK-PYTHON-DS-STATIC-WITNESS. |
| Método | Sink | Resultado |
|---|---|---|
| Registro posterior de un segundo nombre después de que el mapa se llenó | mismo mapa estático | Cerrado. Gana el primero. Lab: later-lookup found=False, el companion sigue siendo solo delta. |
registerPython("alpha") por sesión | runtimeDataSourceBuilders | Aislado. El clon copia solo el runtime. Lab: runtime-isolated=yes. La lectura de alpha por la víctima es found=False. |
Alias de Java parquet | ServiceLoader de DataSourceRegister de Java | Cerrado. Lab: java-alias-closed=yes layer='java'. Lo mismo para csv/json/orc/jdbc. |
| Método | Sink | Privilegio |
|---|---|---|
USING de la víctima tras el registro | workers de Python del driver + executor | Código en todo el clúster como UID de Spark. El lab escribe un archivo testigo en lugar de un payload. |
| Misma ruta | caída / bucle del worker | Disponibilidad como UID de Spark. No es el High. El High es la ejecución de código. |
SQL de Hive Thrift USING sobre una SparkSession compartida | mismo mapa estático | El mismo remanente si Thrift y Connect comparten la JVM del driver. No ejercitado. |
parquet / csv / json / orc / jdbc de Java. Gana ServiceLoader.concatenatePaths rechaza .. y nombres absolutos).pythonExec proporcionado por el cliente. El planificador de Connect usa PYSPARK_PYTHON / python3 del servidor.registerPython. El mapa de runtime es por instancia de DataSourceManager.pyspark local monousuario con una sola sesión. No hay otro inquilino.Fijé apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8). Veintiocho búsquedas en el código fuente. Que el envío de jobs ejecute código del usuario es por diseño. Jackson en History, el shell de la UI, la suplantación local de PySpark y la ruta de recepción RPC de standalone están cerrados en esta revisión. Los artefactos de Connect aíslan los pyfiles bajo un UUID de sesión. El registerPython de runtime es por sesión. El objeto companion no lo es.
SPARK-45917 autorregistra paquetes pyspark_* desde PYTHONPATH. Eso está pensado para conectores instalados por el operador. El remanente es que el worker de búsqueda también hace sys.path.insert del directorio SparkFiles de la sesión que lo activa (worker_util.setup_spark_files), y luego serializa con pickle cualquier DefaultSource de pyspark_* que encuentre en un mapa global del proceso que nunca se refresca.
Camino equivocado: tratar el aislamiento de registerPython como toda la barrera entre inquilinos. El runtime está aislado. El estático no.
cd lab
./run.sh