Apache Spark 4.2.0 - Alto - Remoto - Scrittura di file - Connect archivia hash-fragment dest
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-connect-archive-fragment
Classe: Scrittura di file Portata: Remota
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
I nomi AddArtifacts di Spark Connect nella forma archives/<file>#<fragment> vengono divisi sul carattere hash / cancelletto. I byte dello zip passano attraverso concatenatePaths. Il frammento no. SparkContext.addFile(..., isArchive=true) esegue poi new File(root, uri.getFragment), deleteRecursively(dest) e unpack sul driver. Gli executor costruiscono la stessa dest. SPARK-33530 documentava #name come nome di directory sotto SparkFiles. Un frammento di ../<outside> è la dest.
Connect archives/<file>#../<path> estrae e deleteRecursively al di fuori della directory SparkFiles della sessione come UID di Spark.
| ID | nessuna CVE al momento |
| Classe | Scrittura di file |
| Portata | Remota (client Spark Connect; AddArtifacts con archive=True) |
| CWE | CWE-22 |
| CVSS | Alto: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
| Prodotto | Apache Spark |
| Affetto | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) Connect archives/ dest con frammento hash |
| Auth | Client Connect. Connect predefinito non ha PSK (spark.connect.authenticate.token non impostato). PR:L se PSK / ACL di rete; PR:N su Connect aperto predefinito. |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Ho fissato il tag v4.2.0 peel 32f72996 (2026-07-11, v4.2.0-rc6). Il lab è un gadget Java della divisione hash di StagedArtifact fissata, ArtifactUtils.concatenatePaths, new File(root, fragment), deleteRecursively e estrazione zip contenuta nella dest. HTTP su loopback su 127.0.0.1:18930. PySpark ufficiale copia già un frammento URI nel nome dell'archivio.
Questo non è RCE. I membri dello zip che escono dalla dest vengono rifiutati (classe Hadoop FileUtil.unZip / HADOOP-13649). Ciò che rimane è la dest stessa.
| Metodo | Sink | Privilegio |
|---|---|---|
Connect AddArtifacts nome archives/<file>#../<outside> | SparkContext.addFile dest new File(root, uri.getFragment) poi Utils.unpack (SparkContext.scala:1891-1900) | Integrità (provato in lab): scrive i membri dell'archivio come UID di Spark al di fuori di SparkFiles della sessione. Dest del lab canonica /lab/oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS, uid 1000, dest-outside=yes. |
| Stessa dest, prima dell'estrazione | Utils.deleteRecursively(dest) (SparkContext.scala:1899) | Disponibilità (provato in lab): canary della dest sparito. Lab deleteRecursively=yes; canary.txt sotto la dest non è sopravvissuto. Il sibling /lab/oracle/canary-keep.txt conservato. |
| Stessa primitiva su ogni executor che recupera l'archivio | Dest dell'Executor new File(root, sourceURI.getFragment) poi deleteRecursively + unpack (Executor.scala:1468-1477) | Integrità / disponibilità (inferito): stessa costruzione della dest sugli executor. Il lab è il gadget lato driver. |
Nome dell'artefatto files/../../... o archives/../../... | ArtifactUtils.concatenatePaths (ArtifactUtils.scala:26-39) | Chiuso (provato in lab): INVALID_ARTIFACT_PATH. |
Frammento assoluto Unix /abs | new File(root, "/abs") | Chiuso su Unix (provato in lab): il parent non viene ignorato; la dest si unisce sotto root. Dest del lab /lab/root/lab/oracle/abs-..., outside=false, canary abs conservato. |
Aggiunta jars/ / classes/ della propria sessione | URLClassLoader della sessione | Chiuso come residuo: il caricamento di JAR della propria sessione è by design. Non è questa la scoperta. |
| Caricamento di jar cross-sessione | Isolamento Connect spark.sql.artifact.isolation.enabled=true | Chiuso: le sessioni isolate leggono addedJars(sessionUUID). |
Provato in lab vs inferito: dest con frammento hash al di fuori di SparkFiles, canary della dest cancellato, canary sibling conservato, concatenazione dei nomi files/ e archives/ rifiutata, frammento assoluto Unix che si unisce sotto root, uid 1000, estrazione zip contenuta nella dest: lab. Recupero della stessa dest da parte dell'executor: inferito (stesso costruttore). Sovrascrittura di una classe caricata successivamente sul classpath del driver: catena inferita, non dimostrata, non gonfiata a RCE.
Richiede: un client Connect che possa eseguire AddArtifacts con archive=True. PySpark ufficiale spark.addArtifact(..., archive=True) mette già un frammento URI non vuoto nel nome (python/pyspark/sql/connect/client/artifact.py:223-236). Connect predefinito non ha PSK.
| Superficie | Lab |
|---|---|
Nome artefatto .. / assoluto attraverso concatenatePaths | files-concat=rejected INVALID_ARTIFACT_PATH |
Nome archives/ walk archives/../../... | stesso INVALID_ARTIFACT_PATH |
Unix new File(root, "/abs") | dest sotto root; canary oracle abs conservato |
| Zip-slip di membro zip sotto una dest contenuta | il gadget rifiuta i membri che sfuggono alla dest (classe HADOOP-13649). Non questo residuo. |
Concatenazione di percorso files/ (senza divisione hash) | il frammento non viene preso; il nome è comunque concatenatePaths |
| Isolamento jar cross-sessione | attivo per impostazione predefinita per Connect |
| SQL / Python non autenticato su Connect aperto | invio job by-design (SECURITY.md FAQ). Non questo residuo. |
| Invio job / caricamento modello ML | by design |
StagedArtifact divide solo i nomi che iniziano con archives più il File.separator del server, e solo su #:
val splits = name.split("#")
if (splits.length == 2) {
(splits(0), Some(splits(1)))
}
splits(0) viene preparato tramite concatenatePaths. splits(1) viene mantenuto come fragment. ArtifactManager collega quel frammento all'URI del file-server della sessione e chiama SparkContext.addArchive. La dest è new File(root, uri.getFragment). I test SPARK-33530 usano sc.addArchive(s"${jarFile.getAbsolutePath}#foo") come nome di directory sotto SparkFiles.
La prima passata di hunting sosteneva che un frammento assoluto Unix ignora root (ignoramento del parent di java.io.File per figli assoluti). Su Unix, new File(root, "/abs") rimuove la barra iniziale e si unisce sotto root. Il parent viene ignorato solo su Windows. Il walk High su questo pin è #../.
cd lab
./run.sh
Loopback 127.0.0.1:18930. Progetto Compose spark-connect-archive-fragment. Immagine eclipse-temurin:21-jdk-jammy, uid runtime 1000. Witness SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS. Ultima riga:
SUCCESS SPARK-CONNECT-ARCHIVE-FRAGMENT how=gadget dest-outside=yes deleteRecursively=yes files-concat=rejected uid=1000 SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS