
Apache Spark 4.2.0 - Alta - Remoto - Privilégio remanescente - mapa global de processo do Python Data Source
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-python-ds-static
Classe: Privilégio residual Alcance: Remoto
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
DataSourceManager.dataSourceBuilders é um mapa JVM-global em que o primeiro vence. O SPARK-45917 registra automaticamente Data Sources Python pyspark_* a partir do sys.path do worker de lookup. O isolamento de sessão do Connect mantém os artefatos sob SparkFiles/<sessionUUID>/, e o registerPython por sessão permanece em runtimeDataSourceBuilders. O primeiro lookup ainda coloca o diretório SparkFiles da sessão que disparou no sys.path, serializa (pickle) cada DefaultSource e armazena essas classes em cache no objeto companheiro. Depois, o Connect SQL (ou Hive Thrift SQL) read.format(name) / USING name executa essa classe como o Spark UID sem enviar o proto Python.
O plantador é um tenant Python do Connect que já tem permissão de Python. A vítima é um tenant somente-SQL no mesmo driver Connect. Os aliases Java (parquet, csv, json, orc, jdbc) permanecem no caminho do ServiceLoader Java. Plantações posteriores, depois que o mapa está preenchido, não são atualizadas. AC:H é o primeiro lookup mais um nome que não é um alias Java.
| ID | ainda sem CVE |
| Classe | Privilégio residual (Data Source Python entre sessões como Spark UID; não é envio de job não autenticado) |
| Alcance | Remoto (driver Spark Connect compartilhado) |
| 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 |
| Produto | Apache Spark |
| Afetado | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) registro estático de Data Source Python |
| Autenticação | Tenant Python do Connect (plantador) mais um usuário posterior somente-SQL do Connect / Hive Thrift SQL (vítima) |
| Licença | GNU Affero GPL v3.0 |
| Lab | apenas 127.0.0.1:18970. Arquivo testemunha. Gadget do mapa companheiro. |
Manter uma sessão Python do Spark Connect em um driver compartilhado. Plantar pyfiles/pyspark_<name>.py cujo DefaultSource.name() seja um nome curto de conector ausente (delta, iceberg, hudi, ...). Disparar o primeiro lookup de Data Source Python nessa sessão. Toda sessão posterior na mesma JVM que faça read.format(name) ou SQL USING name instancia a classe serializada como o Spark UID no worker do planner do driver e nos executores.
Esse é o conjunto completo de privilégios. O plantador já executa Python. O residual é que um tenant somente-SQL, que nunca enviou REGISTER_DATA_SOURCE / proto de UDF Python, ainda executa essa classe.
| Método | Sink | Privilégio |
|---|---|---|
Plantador: AddArtifacts com nome pyfiles/pyspark_<name>.py e então primeiro format(name) / lookup | companheiro DataSourceManager.dataSourceBuilders (objeto DataSourceManager.scala) mais lookup_data_sources.py iter_modules de pyspark_* | Armazenar em cache o DefaultSource serializado da sessão que disparou, em toda a JVM. Lab: layer='static' trigger='attacker' first-wins=yes. |
Vítima: SQL USING <name> / spark.read.format(name) em uma sessão que nunca plantou | PythonDataSourceV2 createDataSourceInPython | Executar essa classe como Spark UID. Lab: planted_by='attacker' witness_written=True static-cross-session=yes. O arquivo oráculo contém SPARK-PYTHON-DS-STATIC-WITNESS. |
| Método | Sink | Resultado |
|---|---|---|
| Plantação posterior de um segundo nome depois que o mapa foi preenchido | mesmo mapa estático | Fechado. O primeiro vence. Lab: later-lookup found=False, companheiro ainda apenas delta. |
registerPython("alpha") por sessão | runtimeDataSourceBuilders | Isolado. O clone copia apenas o runtime. Lab: runtime-isolated=yes. A leitura de alpha pela vítima é found=False. |
Alias Java parquet | DataSourceRegister ServiceLoader Java | Fechado. Lab: java-alias-closed=yes layer='java'. O mesmo para csv/json/orc/jdbc. |
| Método | Sink | Privilégio |
|---|---|---|
Vítima USING após a plantação | workers Python do driver + executor | Código em todo o cluster como Spark UID. O lab escreve um arquivo testemunha em vez de um payload. |
| Mesmo caminho | crash / spin do worker | Disponibilidade como Spark UID. Não é o Alto. O Alto é a execução de código. |
Hive Thrift SQL USING em um SparkSession compartilhado | mesmo mapa estático | Mesmo residual se Thrift e Connect compartilharem a JVM do driver. Não exercitado. |
parquet / csv / json / orc / jdbc Java. O ServiceLoader vence.concatenatePaths rejeita .. e nomes absolutos).pythonExec fornecido pelo cliente. O planner do Connect usa PYSPARK_PYTHON / python3 do servidor.registerPython. O mapa de runtime é por instância de DataSourceManager.pyspark local de usuário único com uma sessão. Não há outro tenant.Fixe a versão apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8). Vinte e oito buscas no código-fonte. O envio de job executando código do usuário é por design. Histórico Jackson, shell da UI, impersonação local do PySpark e caminho de recebimento RPC standalone estão fechados nesta análise. Os artefatos do Connect isolam pyfiles sob um UUID de sessão. O registerPython de runtime é por sessão. O objeto companheiro não é.
O SPARK-45917 registra automaticamente pacotes pyspark_* a partir do PYTHONPATH. Isso é intencional para conectores instalados pelo operador. O residual é que o worker de lookup também faz sys.path.insert do diretório SparkFiles da sessão que disparou (worker_util.setup_spark_files), e então serializa qualquer DefaultSource pyspark_* que encontrar em um mapa global de processo que nunca é atualizado.
Caminho errado: tratar o isolamento do registerPython como toda a barreira entre tenants. O runtime é isolado. O estático não é.
cd lab
./run.sh
Loopback 127.0.0.1:18970. Projeto Compose spark-python-ds-static. Testemunha SPARK-PYTHON-DS-STATIC-WITNESS. Gadget Python de object DataSourceManager mais mapas de runtime por sessão. Plantar pyspark_delta.py apenas no SparkFiles do atacante. O lookup do atacante preenche o companheiro. Plantação posterior de iceberg não é atualizada. A leitura de delta pela vítima escreve o oráculo. registerPython("alpha") não vaza. parquet Java permanece Java.
Última linha:
SUCCESS SPARK-PYTHON-DS-STATIC static-cross-session=yes runtime-isolated=yes first-wins=yes java-alias-closed=yes SPARK-PYTHON-DS-STATIC-WITNESS