
Apache Spark 4.2.0 - Hoch - Remote - Privilegien-Reste - prozessglobale Python-Datenquellen-Zuordnung
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-python-ds-static
Write-up: https://abraxaslabs.tech/research/spark-python-ds-static
Klasse: Privilegien-Überbleibsel Reichweite: Remote
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
DataSourceManager.dataSourceBuilders ist eine JVM-globale First-Wins-Map. SPARK-45917 registriert pyspark_* Python Data Sources automatisch aus dem sys.path des Lookup-Workers. Die Connect-Session-Isolation hält Artefakte unter SparkFiles/<sessionUUID>/, und das sitzungsbezogene registerPython bleibt auf runtimeDataSourceBuilders. Der erste Lookup setzt dennoch das SparkFiles-Verzeichnis der auslösenden Session auf sys.path, pickelt jede DefaultSource und cached diese Klassen im Companion-Object. Späteres Connect-SQL (oder Hive-Thrift-SQL) read.format(name) / USING name führt diese Klasse als Spark-UID aus, ohne Python-Proto zu senden.
Der Planter ist ein Connect-Python-Mandant, dem Python bereits erlaubt ist. Das Opfer ist ein reiner SQL-Mandant auf demselben Connect-Driver. Java-Aliase (parquet, csv, json, orc, jdbc) bleiben auf dem Java-ServiceLoader-Pfad. Spätere Plants nach dem Füllen der Map werden nicht aktualisiert. AC:H ist der erste Lookup plus ein Name, der kein Java-Alias ist.
| ID | noch keine CVE |
| Klasse | Privilegien-Überbleibsel (Cross-Session Python Data Source als Spark-UID; kein unauth Job-Submit) |
| Reichweite | Remote (gemeinsamer Spark-Connect-Driver) |
| CWE | CWE-269, CWE-502 |
| CVSS | Hoch: 8.8 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:H/A:H |
| Produkt | Apache Spark |
| Betroffen | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) Python Data Source Static Registry |
| Auth | Connect-Python-Mandant (Planter) plus ein späterer reiner SQL-Connect-/Hive-Thrift-SQL-Benutzer (Opfer) |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1:18970. Witness-Datei. Gadget der Companion-Map. |
Eine Spark-Connect-Python-Session auf einem gemeinsamen Driver halten. pyfiles/pyspark_<name>.py platzieren, dessen DefaultSource.name() ein nicht vorhandener Connector-Kurzname ist (delta, iceberg, hudi, ...). Den ersten Python-Data-Source-Lookup auf dieser Session auslösen. Jede spätere Session in derselben JVM, die read.format(name) oder SQL USING name ausführt, instanziiert die gepickelte Klasse als Spark-UID auf dem Driver-Planner-Worker und auf den Executors.
Das ist die vollständige Privilegienmenge. Der Planter führt bereits Python aus. Das Überbleibsel besteht darin, dass ein reiner SQL-Mandant, der nie REGISTER_DATA_SOURCE / Python-UDF-Proto gesendet hat, diese Klasse dennoch ausführt.
| Methode | Sink | Privileg |
|---|---|---|
Planter: AddArtifacts Name pyfiles/pyspark_<name>.py, dann erstes format(name) / Lookup | Companion DataSourceManager.dataSourceBuilders (DataSourceManager.scala-Object) plus lookup_data_sources.py iter_modules von pyspark_* | Die gepickelte DefaultSource der auslösenden Session JVM-weit cachen. Lab: layer='static' trigger='attacker' first-wins=yes. |
Opfer: SQL USING <name> / spark.read.format(name) auf einer Session, die nie gepflanzt hat | PythonDataSourceV2 createDataSourceInPython | Diese Klasse als Spark-UID ausführen. Lab: planted_by='attacker' witness_written=True static-cross-session=yes. Oracle-Datei enthält SPARK-PYTHON-DS-STATIC-WITNESS. |
| Methode | Sink | Ergebnis |
|---|---|---|
| Späteres Einpflanzen eines zweiten Namens, nachdem die Map gefüllt wurde | dieselbe statische Map | Geschlossen. First-wins. Lab: later-lookup found=False, Companion weiterhin nur delta. |
Pro-Session registerPython("alpha") | runtimeDataSourceBuilders | Isoliert. Clone kopiert nur Runtime. Lab: runtime-isolated=yes. Victim-Lesen von alpha ist found=False. |
Java-Alias parquet | Java DataSourceRegister ServiceLoader | Geschlossen. Lab: java-alias-closed=yes layer='java'. Dasselbe für csv/json/orc/jdbc. |
| Methode | Sink | Privileg |
|---|---|---|
Victim USING nach dem Einpflanzen | Driver + Executor Python-Worker | Clusterweiter Code als Spark-UID. Das Lab schreibt eine Witness-Datei statt einer Payload. |
| Derselbe Pfad | Worker-Absturz / Spin | Verfügbarkeit als Spark-UID. Nicht das High. Das High ist die Code-Exec. |
Hive Thrift SQL USING auf einer geteilten SparkSession | dieselbe statische Map | Derselbe Leftover, wenn Thrift und Connect dieselbe Driver-JVM teilen. Nicht ausgeführt. |
parquet / csv / json / orc / jdbc-Hijack. ServiceLoader gewinnt.concatenatePaths lehnt .. und absolute Namen ab).pythonExec. Connect-Planner verwendet Server-PYSPARK_PYTHON / python3.registerPython. Runtime-Map ist pro DataSourceManager-Instanz.pyspark mit einer Session. Es gibt keinen anderen Tenant.Ich habe apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) gepinnt. Achtundzwanzig Source-Hunts. Job-Submit, der Benutzercode ausführt, ist by design. History Jackson, UI-Shell, PySpark-lokale Impersonation und Standalone-RPC-Receive-Path sind bei diesem Peel geschlossen. Connect-Artefakte isolieren pyfiles unter einer Session-UUID. Runtime-registerPython ist pro Session. Das Companion-Objekt nicht.
SPARK-45917 registriert pyspark_*-Pakete automatisch aus PYTHONPATH. Das ist für operator-installierte Connectoren gedacht. Der Leftover besteht darin, dass der Lookup-Worker außerdem das SparkFiles-Verzeichnis der auslösenden Session per sys.path.insert einfügt (worker_util.setup_spark_files) und dann jede gefundene pyspark_* DefaultSource in eine prozessglobale Map pickelt, die nie aktualisiert wird.
Fehlweg: registerPython-Isolation als die gesamte Tenant-Barriere zu behandeln. Runtime ist isoliert. Static nicht.
cd lab
./run.sh
Loopback 127.0.0.1:18970. Compose-Projekt spark-python-ds-static. Witness SPARK-PYTHON-DS-STATIC-WITNESS. Python-Gadget von object DataSourceManager plus sitzungsbezogene Runtime-Maps. Platziere pyspark_delta.py nur in den SparkFiles des Angreifers. Der Lookup des Angreifers füllt den Companion. Ein späteres Platzieren von iceberg aktualisiert nicht. Der read des Opfers von delta schreibt das Oracle. registerPython("alpha") leakt nicht. Java parquet bleibt Java.
Letzte Zeile: