
Apache Spark 4.2.0 - Alto - Remoto - Lettura di file - Lo zip della cronologia segue un symlink piantato
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-zip-symlink
Classe: Lettura di file Portata: Remota
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
CVE-2025-54920 ha bloccato Jackson nell'History Server, così sparkEventFromJson costruisce solo sottoclassi di SparkListenerEvent. Il rolling zipEventLogFiles scarica comunque ogni figlio di listStatus tramite FileSystem.open. LocalFileSystem segue un symlink POSIX. Un figlio piantato in eventlog_v2_* viene impacchettato nella GET non autenticata /api/v1/applications/{id}/logs. Il corpo del membro dello zip è il target dello SHS-UID. Il nome della voce è il basename del symlink.
Questa non è una riproposizione della RCE di Jackson. Jackson resta chiuso. Questa non è una RCE sull'host. Integrità e disponibilità sono nulle.
| ID | nessuna CVE al momento |
| Classe | Lettura di file (residuo dello zip rolling del modello di vittima CVE-2025-54920) |
| Portata | Remota (scrittore della directory dei log della History più GET non autenticata dello zip) |
| CWE | CWE-59, CWE-22 |
| CVSS | Alto: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Prodotto | Apache Spark |
| Affetto | tag 4.2.0 v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| Auth | PR:L = scrittura su spark.history.fs.logDirectory (stessa vittima di 54920). Le ACL della History UI sono disattivate di default. |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1:18980 |
Chi può scrivere in spark.history.fs.logDirectory (default file:/tmp/spark-events) può piantare una directory rolling che l'History Server zipperà. I writer rolling sono attivi di default. Le ACL della History UI sono disattivate di default. L'UID del processo SHS è il confused deputy.
| Privilegio | Metodo / sink | Provato in lab o dedotto |
|---|---|---|
| Riservatezza: leggere un file dell'host che l'UID SHS può aprire | Piantare eventlog_v2_<id>/ con appstatus_* validi più JSON events_* (SparkListenerLogStart / SparkListenerApplicationStart) più un figlio symlink il cui target risiede fuori dalla log dir. Attendere la scansione. GET non autenticata GET /api/v1/applications/{id}/logs (OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open) | Provato in lab. Il corpo del membro dello zip eventlog_v2_app-2026-0001/planted-secret è SPARK-HISTORY-ZIP-SYMLINK-WITNESS. Il membro events non ha witness. Il nome della voce resta il basename del symlink, non il percorso del target. |
Riservatezza: event log di un tenant adiacente che l'attaccante non può open (660) | Stesso zip, il target del symlink è il log di un'altra app | Dedotto. Stesso FileSystem.open. Non esercitato. |
Riservatezza: keytab, spark-env.sh, /etc/passwd se SHS può leggerli | Stesso zip | Dedotto. L'oracolo è un /secret/host-secret dedicato, non quei file. |
| Integrità dei file di SHS o dell'host | lo zip viene letto nel corpo HTTP | Chiuso. Provato in lab: nessuno. |
| Disponibilità dei file di SHS o dell'host | lo zip non cancella né tronca il target | Chiuso. Provato in lab: nessuno. |
| RCE sull'host / costruzione di gadget Jackson | JsonProtocol.sparkEventFromJson | Chiuso. Richiede SparkListenerEvent. Questo lab pianta solo Event JSON benigni. |
Zip slip di ../ nel nome della voce | Path.getName() | Chiuso. Un filename POSIX non può contenere /. La voce è eventlog_v2_<id>/<basename>. |
Fuga della stringa di percorso di resolveLogPath | new Path(logDir, attempt.logPath) | Chiuso. L'oggetto Path resta sotto la log dir configurata. La fuga è l'inode. |
| Riscrittura di compaction / cancellazione del target da parte del cleaner | spark.history.fs.eventLog.rolling.maxFilesToRetain default Integer.MAX_VALUE; cleaner disattivato di default | Chiuso. La fs.delete locale di un symlink scollega il link, non il target. |
| Invio di job non autenticato / caricamento di modelli ML | FAQ di SECURITY.md / docs/ml-security.md | Chiuso. Per design, non questo residuo. |
Ho fissato apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) invece di master o di una RC 4.3. Ventotto ricerche. La RCE di Jackson nella History è chiusa su questa revisione (JsonProtocol.scala:965-972). L'History Server puntava comunque a una log dir scrivibile dall'attaccante, che è lo stesso modello di vittima di CVE-2025-54920.
zipEventLogFiles zippa tutti i files, non solo i file di eventi:
override def zipEventLogFiles(zipStream: ZipOutputStream): Unit = {
val dirEntryName = rootPath.getName + "/"
zipStream.putNextEntry(new ZipEntry(dirEntryName))
files.foreach { file =>
addFileAsZipEntry(zipStream, file.getPath, dirEntryName + file.getPath.getName)
}
}
addFileAsZipEntry è fileSystem.open seguito da transferTo. LocalFileSystem segue i symlink POSIX. getLogDirInfo commenta "handles symbolic links properly" e poi percorre i parent Path non risolti per un nome visualizzato.
Vicoli ciechi già registrati: trattare SPARK-52381 come l'intera storia della History; piantare il witness in events_* (il lab fallisce in quel caso); aspettarsi che la voce dello zip porti il percorso del target; trattare il prefix-join di resolveLogPath come la fuga; provare una classe Event HiveConnection su questa revisione.
python3 spark-history-zip-symlink-Abraxas-Labs.py
oppure
cd lab
./run.sh
Gadget del dump dello zip rolling di EventLogFileReaders.scala, non un History Server completo e non l'immagine ufficiale spark:4.2.0. Non analizza Event JSON tramite Jackson. Loopback 127.0.0.1:18980. Progetto Compose spark-history-zip-symlink. La directory rolling contiene appstatus_* + events_* validi (senza witness) più planted-secret -> /secret/host-secret.
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
Impacchettare solo i figli events_* / appstatus_*, non ogni nome di listStatus. Aprire con no-follow (O_NOFOLLOW / opzioni no-symlink di Hadoop) e rifiutare un figlio il cui realpath è fuori da spark.history.fs.logDirectory. Un helper per il nome visualizzato non è un controllo di realpath.