
Apache Spark 4.2.0 - उच्च - रिमोट - फ़ाइल पढ़ना - History zip रखे गए symlink का अनुसरण करता है
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-zip-symlink
Class: File read Reach: Remote
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
CVE-2025-54920 ने History Server Jackson को लॉक कर दिया ताकि sparkEventFromJson केवल SparkListenerEvent सबक्लास ही बनाए। रोलिंग zipEventLogFiles अभी भी हर listStatus चाइल्ड को FileSystem.open के ज़रिए डंप करता है। LocalFileSystem एक POSIX symlink का अनुसरण करता है। eventlog_v2_* में रखा गया चाइल्ड unauth GET /api/v1/applications/{id}/logs में पैक हो जाता है। zip सदस्य का बॉडी SHS-UID टारगेट होता है। एंट्री नाम symlink का basename होता है।
यह Jackson RCE का दोबारा फाइल नहीं है। Jackson बंद ही रहता है। यह होस्ट RCE नहीं है। Integrity और availability कुछ नहीं हैं।
| ID | अभी कोई CVE नहीं |
| Class | File read (CVE-2025-54920 विक्टिम मॉडल का रोलिंग zip अवशेष) |
| Reach | Remote (History log dir का लेखक और zip का unauth GET) |
| CWE | CWE-59, CWE-22 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Product | Apache Spark |
| Affected | 4.2.0 टैग v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| Auth | PR:L = spark.history.fs.logDirectory में लिखना (54920 जैसा ही विक्टिम)। History UI ACLs डिफ़ॉल्ट रूप से बंद। |
| License | GNU Affero GPL v3.0 |
| Lab | केवल 127.0.0.1:18980 |
जो कोई spark.history.fs.logDirectory (डिफ़ॉल्ट file:/tmp/spark-events) में लिख सकता है, वह एक रोलिंग dir रख सकता है जिसे History Server zip करेगा। रोलिंग राइटर डिफ़ॉल्ट रूप से चालू। History UI ACLs डिफ़ॉल्ट रूप से बंद। SHS प्रोसेस UID ही confused deputy है।
| Privilege | Method / sink | Lab-proven या inferred |
|---|---|---|
| Confidentiality: SHS UID जिस होस्ट फाइल को खोल सकता है उसे पढ़ना | eventlog_v2_<id>/ रखें जिसमें वैध appstatus_* और events_* JSON (SparkListenerLogStart / SparkListenerApplicationStart) के साथ एक symlink चाइल्ड हो जिसका टारगेट log dir के बाहर हो। स्कैन की प्रतीक्षा करें। Unauth GET /api/v1/applications/{id}/logs (OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open) | Lab-proven। Zip सदस्य eventlog_v2_app-2026-0001/planted-secret का बॉडी SPARK-HISTORY-ZIP-SYMLINK-WITNESS है। Events सदस्य में कोई witness नहीं। एंट्री नाम symlink का basename ही रहता है, टारगेट पाथ नहीं। |
Confidentiality: सिबलिंग टेनेंट का event log जिसे हमलावर open (660) नहीं कर सकता | वही zip, symlink टारगेट दूसरे ऐप का log | Inferred। वही FileSystem.open। आज़माया नहीं गया। |
Confidentiality: keytab, spark-env.sh, /etc/passwd यदि SHS उन्हें पढ़ सकता है | वही zip | Inferred। Oracle एक समर्पित /secret/host-secret है, वे फाइलें नहीं। |
| SHS या होस्ट फाइलों की Integrity | zip को HTTP बॉडी में पढ़ा जाता है | Closed। Lab-proven कुछ नहीं। |
| SHS या होस्ट फाइलों की Availability | zip टारगेट को डिलीट या ट्रंकेट नहीं करता | Closed। Lab-proven कुछ नहीं। |
| Host RCE / Jackson gadget construct | JsonProtocol.sparkEventFromJson | Closed। SparkListenerEvent आवश्यक। यह लैब केवल benign Event JSON रखती है। |
एंट्री नाम में ../ का zip slip | Path.getName() | Closed। POSIX फाइलनाम में / नहीं हो सकता। एंट्री eventlog_v2_<id>/<basename> है। |
resolveLogPath का path-string escape | new Path(logDir, attempt.logPath) | Closed। Path ऑब्जेक्ट कॉन्फ़िगर किए गए log dir के अंदर ही रहता है। Escape inode है। |
| टारगेट का compaction rewrite / cleaner delete | spark.history.fs.eventLog.rolling.maxFilesToRetain डिफ़ॉल्ट Integer.MAX_VALUE; cleaner डिफ़ॉल्ट बंद | Closed। symlink का लोकल fs.delete लिंक को unlink करता है, टारगेट को नहीं। |
| Unauth job submit / ML model load | SECURITY.md FAQ / docs/ml-security.md | Closed। डिज़ाइन से, यह अवशेष नहीं। |
मैंने apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) को पिन किया, master या 4.3 RC को नहीं। अट्ठाईस hunts। इस peel पर Jackson History RCE बंद है (JsonProtocol.scala:965-972)। History Server अभी भी हमलावर-लेखन-योग्य log dir की ओर इशारा करता था, जो CVE-2025-54920 जैसा ही विक्टिम मॉडल है।
zipEventLogFiles सभी files को zip करता है, केवल event फाइलों को नहीं:
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 फिर transferTo है। LocalFileSystem POSIX symlinks का अनुसरण करता है। getLogDirInfo टिप्पणी करता है "handles symbolic links properly" और फिर display नाम के लिए unresolved Path parents पर चलता है।
पहले से दर्ज गलत रास्ते: SPARK-52381 को पूरी History कहानी मानना; witness को events_* में रखना (लैब उसमें फेल होती है); zip एंट्री के टारगेट पाथ ले जाने की अपेक्षा; resolveLogPath prefix-join को escape मानना; इस pin पर HiveConnection Event क्लास आज़माना।
python3 spark-history-zip-symlink-Abraxas-Labs.py
या
cd lab
./run.sh
EventLogFileReaders.scala रोलिंग zip डंप का gadget, पूरा History Server नहीं और आधिकारिक spark:4.2.0 इमेज नहीं। यह Jackson के ज़रिए Event JSON पार्स नहीं करता। Loopback 127.0.0.1:18980। Compose प्रोजेक्ट spark-history-zip-symlink। रोलिंग dir में वैध appstatus_* + events_* (witness रहित) और planted-secret -> /secret/host-secret है।
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
केवल events_* / appstatus_* चाइल्ड पैक करें, हर listStatus नाम नहीं। no-follow (O_NOFOLLOW / Hadoop no-symlink विकल्प) के साथ खोलें और ऐसे चाइल्ड को अस्वीकार करें जिसका realpath spark.history.fs.logDirectory के बाहर हो। display-name हेल्पर realpath जाँच नहीं है।