
Apache Spark 4.2.0 - उच्च - रिमोट - फ़ाइल लेखन - Connect archives hash-fragment dest
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-connect-archive-fragment
Class: File write Reach: Remote
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
Spark Connect AddArtifacts नाम archives/<file>#<fragment> के रूप में होते हैं जो hash / number-sign वर्ण पर विभाजित होते हैं। zip bytes concatenatePaths से गुज़रते हैं। fragment नहीं गुज़रता। SparkContext.addFile(..., isArchive=true) फिर driver पर new File(root, uri.getFragment), deleteRecursively(dest), और unpack करता है। Executors वही dest बनाते हैं। SPARK-33530 ने #name को SparkFiles के अंतर्गत एक directory नाम के रूप में प्रलेखित किया। ../<outside> का fragment ही dest है।
Connect archives/<file>#../<path> Spark UID के रूप में session SparkFiles dir के बाहर unpack और deleteRecursively करता है।
| ID | अभी तक कोई CVE नहीं |
| Class | File write |
| Reach | Remote (Spark Connect client; AddArtifacts with archive=True) |
| CWE | CWE-22 |
| CVSS | High: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
| Product | Apache Spark |
| Affected | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) Connect archives/ hash-fragment dest |
| Auth | Connect client. Default Connect में कोई PSK नहीं (spark.connect.authenticate.token unset)। PSK / network ACL होने पर PR:L; default open Connect पर PR:N। |
| License | GNU Affero GPL v3.0 |
| Lab | केवल 127.0.0.1 |
मैंने tag v4.2.0 peel 32f72996 (2026-07-11, v4.2.0-rc6) pin किया। Lab pinned StagedArtifact hash split, ArtifactUtils.concatenatePaths, new File(root, fragment), deleteRecursively, और dest-contained zip unpack का एक Java gadget है। 127.0.0.1:18930 पर loopback HTTP। Official PySpark पहले से ही URI fragment को archive नाम में copy करता है।
यह RCE नहीं है। dest से बाहर निकलने वाले zip members अस्वीकार किए जाते हैं (Hadoop FileUtil.unZip / HADOOP-13649 class)। बचा हुआ dest स्वयं है।
| Method | Sink | Privilege |
|---|---|---|
Connect AddArtifacts नाम archives/<file>#../<outside> | SparkContext.addFile dest new File(root, uri.getFragment) फिर Utils.unpack (SparkContext.scala:1891-1900) | Integrity (lab-proven): session SparkFiles के बाहर Spark UID के रूप में archive members लिखें। Lab dest canonical /lab/oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS, uid 1000, dest-outside=yes। |
| वही dest, unpack से पहले | Utils.deleteRecursively(dest) (SparkContext.scala:1899) | Availability (lab-proven): dest canary चला गया। Lab deleteRecursively=yes; dest के अंतर्गत canary.txt नहीं बचा। sibling /lab/oracle/canary-keep.txt बचा रहा। |
| archive fetch करने वाले प्रत्येक executor पर वही primitive | Executor dest new File(root, sourceURI.getFragment) फिर deleteRecursively + unpack (Executor.scala:1468-1477) | Integrity / availability (inferred): executors पर वही dest construction। Lab driver-side gadget है। |
Artifact name files/../../... या archives/../../... | ArtifactUtils.concatenatePaths (ArtifactUtils.scala:26-39) | Closed (lab-proven): INVALID_ARTIFACT_PATH। |
Unix absolute fragment /abs | new File(root, "/abs") | Unix पर Closed (lab-proven): parent को ignore नहीं किया जाता; dest root के अंतर्गत join होता है। Lab dest /lab/root/lab/oracle/abs-..., outside=false, abs canary बचा रहा। |
Own-session jars/ / classes/ add | session URLClassLoader | एक leftover के रूप में Closed: own-session JAR load by design है। यह finding नहीं। |
| Cross-session jar load | Connect isolation spark.sql.artifact.isolation.enabled=true | Closed: isolated sessions addedJars(sessionUUID) पढ़ते हैं। |
Lab-proven vs inferred: SparkFiles के बाहर hash-fragment dest, dest canary deleted, sibling canary kept, files/ और archives/ name concat rejected, Unix absolute fragment root के अंतर्गत join, uid 1000, dest-contained zip unpack: lab। वही dest का Executor fetch: inferred (same constructor)। driver classpath पर बाद में load होने वाली class का overwrite: inferred chain, प्रदर्शित नहीं, RCE तक नहीं बढ़ाया गया।
Requires: एक Connect client जो archive=True के साथ AddArtifacts कर सके। Official PySpark spark.addArtifact(..., archive=True) पहले से ही नाम में एक non-empty URI fragment डालता है (python/pyspark/sql/connect/client/artifact.py:223-236)। Default Connect में कोई PSK नहीं।
| Surface | Lab |
|---|---|
Artifact name .. / absolute concatenatePaths के माध्यम से | files-concat=rejected INVALID_ARTIFACT_PATH |
archives/ name walk archives/../../... | वही INVALID_ARTIFACT_PATH |
Unix new File(root, "/abs") | dest root के अंतर्गत; abs oracle canary बचा रहा |
| Contained dest के अंतर्गत Zip member zip-slip | gadget dest से बाहर निकलने वाले members को अस्वीकार करता है (HADOOP-13649 class)। यह leftover नहीं। |
files/ path concat (कोई hash split नहीं) | fragment नहीं लिया जाता; name अभी भी concatenatePaths |
| Cross-session jar isolation | Connect के लिए default on |
| Open Connect पर Unauth SQL / Python | by-design job submit (SECURITY.md FAQ)। यह leftover नहीं। |
| Job submit / ML model load | by design |
StagedArtifact केवल उन नामों को split करता है जो archives से शुरू होते हैं और साथ में server File.separator, और केवल # पर:
val splits = name.split("#")
if (splits.length == 2) {
(splits(0), Some(splits(1)))
}
splits(0) को concatenatePaths के माध्यम से stage किया जाता है। splits(1) को fragment के रूप में रखा जाता है। ArtifactManager उस fragment को session file-server URI से जोड़ता है और SparkContext.addArchive को call करता है। Dest new File(root, uri.getFragment) है। SPARK-33530 sc.addArchive(s"${jarFile.getAbsolutePath}#foo") को SparkFiles के अंतर्गत एक directory नाम के रूप में test करता है।
Hunt first pass ने दावा किया कि Unix absolute fragment root को ignore करता है (java.io.File absolute children के लिए parent-ignore)। Unix पर, new File(root, "/abs") leading slash को हटा देता है और root के अंतर्गत join करता है। Parent केवल Windows पर ignore होता है। इस pin पर High walk #../ है।
cd lab
./run.sh
Loopback 127.0.0.1:18930। Compose project spark-connect-archive-fragment। Image eclipse-temurin:21-jdk-jammy, runtime uid 1000। Witness SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS। अंतिम पंक्ति:
SUCCESS SPARK-CONNECT-ARCHIVE-FRAGMENT how=gadget dest-outside=yes deleteRecursively=yes files-concat=rejected uid=1000 SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS
CONTROL: archives/payload.zip /lab/root के अंतर्गत unpack होता है। ATTACK: archives/payload-frag.zip#../oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS dest canonical /lab/oracle/..., canary deleted, pwned.txt uid 1000 के रूप में लिखा गया। Unix absolute fragment root के अंतर्गत join होता है। Stack EXIT पर tear down होता है।