Apache Spark 4.2.0 - Hoch - Remote - Dateischreiben - Connect archives hash-fragment dest
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-connect-archive-fragment
Klasse: Dateischreiben Reichweite: Remote
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
Spark Connect AddArtifacts-Namen der Form archives/<file>#<fragment> werden am Hash- / Raute-Zeichen geteilt. Die Zip-Bytes durchlaufen concatenatePaths. Das Fragment nicht. SparkContext.addFile(..., isArchive=true) führt dann new File(root, uri.getFragment), deleteRecursively(dest) und unpack auf dem Driver aus. Executors bauen dasselbe Ziel. SPARK-33530 dokumentierte #name als Verzeichnisnamen unter SparkFiles. Ein Fragment von ../<outside> ist das Ziel.
Connect archives/<file>#../<path> entpackt und deleteRecursivelyt außerhalb des Session-SparkFiles-Verzeichnisses als die Spark-UID.
| ID | noch keine CVE |
| Klasse | Dateischreiben |
| Reichweite | Remote (Spark Connect Client; AddArtifacts mit archive=True) |
| CWE | CWE-22 |
| CVSS | Hoch: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
| Produkt | Apache Spark |
| Betroffen | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) Connect archives/ Hash-Fragment-Ziel |
| Auth | Connect-Client. Standard-Connect hat kein PSK (spark.connect.authenticate.token nicht gesetzt). PR:L bei PSK / Netzwerk-ACL; PR:N bei standardmäßig offenem Connect. |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Ich habe den Tag v4.2.0 Peel 32f72996 (2026-07-11, v4.2.0-rc6) gepinnt. Das Lab ist ein Java-Gadget des gepinnten StagedArtifact-Hash-Splits, ArtifactUtils.concatenatePaths, new File(root, fragment), deleteRecursively und zielgebundenem Zip-Entpacken. Loopback-HTTP auf 127.0.0.1:18930. Offizielles PySpark kopiert bereits ein URI-Fragment in den Archivnamen.
Dies ist kein RCE. Zip-Member, die aus dem Ziel herauslaufen, werden abgelehnt (Hadoop FileUtil.unZip / HADOOP-13649-Klasse). Der Überrest ist das Ziel selbst.
| Methode | Sink | Privileg |
|---|---|---|
Connect AddArtifacts-Name archives/<file>#../<outside> | SparkContext.addFile-Ziel new File(root, uri.getFragment) dann Utils.unpack (SparkContext.scala:1891-1900) | Integrität (lab-belegt): Archiv-Member als die Spark-UID außerhalb der Session-SparkFiles schreiben. Lab-Ziel kanonisch /lab/oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS, uid 1000, dest-outside=yes. |
| Dasselbe Ziel, vor dem Entpacken | Utils.deleteRecursively(dest) (SparkContext.scala:1899) | Verfügbarkeit (lab-belegt): Ziel-Canary weg. Lab deleteRecursively=yes; canary.txt unter dem Ziel hat nicht überlebt. Benachbarte /lab/oracle/canary-keep.txt blieb erhalten. |
| Dasselbe Primitiv auf jedem Executor, der das Archiv abruft | Executor-Ziel new File(root, sourceURI.getFragment) dann deleteRecursively + unpack (Executor.scala:1468-1477) | Integrität / Verfügbarkeit (abgeleitet): dieselbe Zielkonstruktion auf Executors. Lab ist das driver-seitige Gadget. |
Artefakt-Name files/../../... oder archives/../../... | ArtifactUtils.concatenatePaths (ArtifactUtils.scala:26-39) | Geschlossen (lab-belegt): INVALID_ARTIFACT_PATH. |
Unix-absolutes Fragment /abs | new File(root, "/abs") | Geschlossen auf Unix (lab-belegt): Parent wird nicht ignoriert; Ziel fügt sich unter root ein. Lab-Ziel /lab/root/lab/oracle/abs-..., outside=false, abs-Canary erhalten. |
Eigen-Session jars/ / classes/ hinzufügen | Session URLClassLoader | Geschlossen als Überrest: Eigen-Session-JAR-Laden ist by design. Nicht dieser Fund. |
| Cross-Session-JAR-Laden | Connect-Isolation spark.sql.artifact.isolation.enabled=true | Geschlossen: isolierte Sessions lesen addedJars(sessionUUID). |
Lab-belegt vs. abgeleitet: Hash-Fragment-Ziel außerhalb SparkFiles, Ziel-Canary gelöscht, benachbarte Canary erhalten, files/- und archives/-Namenskonkatenation abgelehnt, Unix-absolutes Fragment fügt sich unter root ein, uid 1000, zielgebundenes Zip-Entpacken: Lab. Executor-Abruf desselben Ziels: abgeleitet (derselbe Konstruktor). Überschreiben einer später geladenen Klasse auf dem Driver-Classpath: abgeleitete Kette, nicht demonstriert, nicht zu RCE hochgespielt.
Erfordert: einen Connect-Client, der AddArtifacts mit archive=True ausführen kann. Offizielles PySpark spark.addArtifact(..., archive=True) setzt bereits ein nicht-leeres URI-Fragment in den Namen (python/pyspark/sql/connect/client/artifact.py:223-236). Standard-Connect hat kein PSK.
| Oberfläche | Lab |
|---|---|
Artefakt-Name .. / absolut durch concatenatePaths | files-concat=rejected INVALID_ARTIFACT_PATH |
archives/-Name-Walk archives/../../... | dasselbe INVALID_ARTIFACT_PATH |
Unix new File(root, "/abs") | Ziel unter root; abs-Oracle-Canary erhalten |
| Zip-Member-Zip-Slip unter einem eingegrenzten Ziel | Gadget lehnt Member ab, die das Ziel verlassen (HADOOP-13649-Klasse). Nicht dieser Überrest. |
files/-Pfadkonkatenation (kein Hash-Split) | Fragment wird nicht genommen; Name weiterhin concatenatePaths |
| Cross-Session-JAR-Isolation | standardmäßig an für Connect |
| Unauth SQL / Python auf offenem Connect | by-design Job-Submit (SECURITY.md FAQ). Nicht dieser Überrest. |
| Job-Submit / ML-Modell-Laden | by design |
StagedArtifact teilt nur Namen, die mit archives plus dem Server-File.separator beginnen, und nur an #:
val splits = name.split("#")
if (splits.length == 2) {
(splits(0), Some(splits(1)))
}
splits(0) wird über concatenatePaths gestaged. splits(1) wird als fragment behalten. ArtifactManager hängt dieses Fragment an die Session-File-Server-URI und ruft SparkContext.addArchive auf. Ziel ist new File(root, uri.getFragment). SPARK-33530 testet sc.addArchive(s"${jarFile.getAbsolutePath}#foo") als Verzeichnisnamen unter SparkFiles.
Der erste Hunt-Durchlauf behauptete, ein Unix-absolutes Fragment ignoriere root (java.io.File Parent-Ignore für absolute Kinder). Auf Unix entfernt new File(root, "/abs") den führenden Slash und fügt sich unter root ein. Parent wird nur auf Windows ignoriert. Der High-Walk auf diesem Pin ist #../.
cd lab
./run.sh
Loopback 127.0.0.1:18930. Compose-Projekt spark-connect-archive-fragment. Image eclipse-temurin:21-jdk-jammy, Runtime-uid 1000. Witness SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS. Letzte Zeile:
SUCCESS SPARK-CONNECT-ARCHIVE-FRAGMENT how=gadget dest-outside=yes deleteRecursively=yes files-concat=rejected uid=1000 SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS