Apache Spark 4.2.0 - Alto - Remoto - Scrittura di file - History appStorePath App ID non sanificato
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-store-path
Classe: Scrittura di file (condizionata dalla configurazione) Portata: Remota
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
HistoryServerDiskManager.appStorePath esegue new File(appStoreDir, appId + extension) senza alcuna sanificazione. Utils.sanitizeDirName è solo per i nomi dei file di event-log. L'App ID JSON è asText(). La lista memorizza event.appId.orNull. Lease.commit usa quella stringa. Un App ID ../ scrive un .rdb al di fuori di spark.history.store.path. Il valore predefinito non impostato di store.path è in memoria (nessuna scrittura). Il contenuto è un file di store, non RCE.
| ID | nessuna CVE al momento |
| Classe | Scrittura di file (store su disco della History condizionato dalla configurazione) |
| Portata | Remota (scrittore di event-log più History con spark.history.store.path impostato; ACL della UI disattivate per impostazione predefinita) |
| CWE | CWE-22 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N quando spark.history.store.path è impostato |
| Prodotto | Apache Spark History Server |
| Affetto | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| Auth | scrittore di spark.history.fs.logDirectory (predefinito file:/tmp/spark-events); History deve avere spark.history.store.path impostato |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1:18990. Gadget del join citato. Testimone .rdb al di fuori di store.path. |
Stesso modello di vittima di CVE-2025-54920: History Server puntato a una directory di log in cui l'attaccante può scrivere. Jackson non costruisce più gadget della classe HiveConnection. Il percorso del disk-store continua a fidarsi dell'App ID nel JSON dell'evento.
| Metodo | Sink | Privilegio | Lab vs dedotto |
|---|---|---|---|
Piantare un event log il cui App ID JSON è ../<name> (o ../../oracle/...). Attendere la scansione. GET che carica la UI dell'app / getAppUI / loadDiskStore / createDiskStore | HistoryServerDiskManager.appStorePath poi Lease.commit renameTo | Integrità: creare un percorso .rdb (RocksDB predefinito) o .ldb (LevelDB) al di fuori di {store.path}/apps come UID della History. Provato in lab sul gadget: escaped=yes, il file /work/oracle/SPARK-HISTORY-STORE-PATH-WITNESS.rdb contiene il testimone. Il contenuto è un file di store, non bytecode host scelto dall'attaccante, non una shell. | Provato in lab (scrittura del gadget dei byte testimone al percorso di escape). Il renameTo completo della History di una directory RocksDB è lo stesso join. |
| Stesso App ID quando la destinazione di escape è già una directory | Lease.commit if (dst.isDirectory()) deleteStore(dst) poi Utils.deleteRecursively | Disponibilità: eliminare una directory esistente il cui nome termina già con il suffisso del backend, poi sostituirla con lo store preso in lease. | Dedotto (fissare il commit di HistoryServerDiskManager.scala). Il gadget ha scritto un file, non ha chiamato deleteRecursively. |
spark.history.store.path predefinito non impostato | LOCAL_STORE_DIR createOptional; diskManager = storePath.map | Chiuso. Elenco in memoria. Nessun HistoryServerDiskManager. Provato in lab gated=yes written=no. | Provato in lab |
Applicare Utils.sanitizeDirName all'App ID per primo | helper del nome file di event-log | Rimane dentro apps/. / e : diventano -. Provato in lab negativo. | Provato in lab |
Controllare l'App ID local-control con store.path impostato | stesso join | Scrive {store.path}/apps/local-control.rdb. Rimane dentro apps/. | Provato in lab |
Adiacenti chiusi: store predefinito non impostato (in memoria). Jackson sparkEventFromJson richiede comunque SparkListenerEvent (CVE-2025-54920). Il fallback URL getAppUI eventlog_v2_ + nameForAppAndAttempt è sanificato. La chiave KV della lista non è un percorso filesystem. HybridStore / kvstore mapper non ha Id.CLASS. La History HTTP non accetta un percorso di upload. Compaction disattivata per impostazione predefinita. Cleaner disattivato per impostazione predefinita. Invio di job su un cluster aperto (FAQ di SECURITY.md). Caricamento di modelli ML (docs/ml-security.md). Questo residuo non è RCE sull'host.
Ho fissato Spark 4.2.0, non master, non una RC 4.3. Ventotto ricerche. L'RCE pubblico di History via Jackson è chiuso su questa peel. Gli scrittori di event-log chiamano già Utils.sanitizeDirName quindi il file di log non può contenere /. Il nome del file della disk-cache usa l'App ID grezzo.
private[history] def appStorePath(appId: String, attemptId: Option[String]): File = {
val fileName = appId + attemptId.map("_" + _).getOrElse("") + extension
new File(appStoreDir, fileName)
}
appStoreDir è {spark.history.store.path}/apps. Su Unix new File(parent, "../../oracle/name.rdb") risolve al di fuori del parent. JsonProtocol.applicationStartFromJson prende json.get("App ID").asText(). AppListingListener.onApplicationStart memorizza event.appId.orNull. lease.commit(appId, attempt.info.attemptId) passa quella stringa a appStorePath.
Vicoli ciechi già registrati: trattare store.path predefinito non impostato come una scrittura (è in memoria); aspettarsi che new File(dir, "/abs") su Unix ignori il parent (non lo fa; il residuo è ..); affermare RCE perché il suffisso è .rdb; avviare un History Server completo quando il join è l'oracolo; trattare sanitizeDirName come coprente questo percorso.
python3 spark-history-store-path-Abraxas-Labs.py
oppure
cd lab
./run.sh
Gadget di HistoryServerDiskManager.appStorePath su 127.0.0.1:18990. Estratti del pin sotto lab/pin/. Il History Server completo non viene avviato. Associalo al loopback.
SUCCESS SPARK-HISTORY-STORE-PATH how=gadget escaped=yes gated=yes outside=/work/oracle/SPARK-HISTORY-STORE-PATH-WITNESS.rdb SPARK-HISTORY-STORE-PATH-WITNESS
Eseguire Utils.sanitizeDirName (o rifiutare .. / / / NUL) su appId e attemptId dentro appStorePath prima di new File. Dopo il join, richiedere che dst.getCanonicalFile() rimanga sotto appStoreDir.getCanonicalFile(). spark.history.store.path non impostato rimane il default sicuro.