Apache Spark 4.2.0 - عالي - عن بُعد - كتابة ملف - Connect archives hash-fragment dest
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-connect-archive-fragment
الفئة: كتابة ملف الوصول: عن بُعد
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
أسماء AddArtifacts في Spark Connect من الشكل archives/<file>#<fragment> تُقسَّم على محرف الهاش / علامة الرقم. بايتات الـ zip تمر عبر concatenatePaths. أما الـ fragment فلا. بعد ذلك يقوم SparkContext.addFile(..., isArchive=true) بتنفيذ new File(root, uri.getFragment)، ثم deleteRecursively(dest)، ثم unpack على الـ driver. الـ executors تبني نفس الـ dest. وثّقت SPARK-33530 أن #name هو اسم دليل تحت SparkFiles. والـ fragment من الشكل ../<outside> هو الـ dest.
Connect archives/<file>#../<path> يفكّ الضغط وينفّذ deleteRecursively خارج دليل SparkFiles الخاص بالجلسة بصلاحية Spark UID.
| ID | لا يوجد CVE بعد |
| الفئة | كتابة ملف |
| الوصول | عن بُعد (عميل Spark Connect؛ AddArtifacts مع archive=True) |
| CWE | CWE-22 |
| CVSS | مرتفع: 8.1 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H |
| المنتج | Apache Spark |
| المتأثر | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) Connect archives/ hash-fragment dest |
| المصادقة | عميل Connect. Connect الافتراضي بلا PSK (spark.connect.authenticate.token غير مضبوط). PR:L في حال وجود PSK / ACL شبكي؛ PR:N على Connect المفتوح الافتراضي. |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1 فقط |
ثبّتُ الوسم v4.2.0 peel 32f72996 (2026-07-11، v4.2.0-rc6). المختبر عبارة عن gadget بلغة Java لتقسيم الهاش في StagedArtifact المثبّت، وArtifactUtils.concatenatePaths، وnew File(root, fragment)، وdeleteRecursively، وفك ضغط zip محتوى داخل الـ dest. HTTP على loopback عبر 127.0.0.1:18930. PySpark الرسمي ينسخ بالفعل fragment الـ URI إلى اسم الأرشيف.
هذا ليس RCE. أعضاء الـ zip التي تخرج من الـ dest تُرفض (فئة Hadoop FileUtil.unZip / HADOOP-13649). ما يتبقى هو الـ dest نفسه.
| الطريقة | الـ Sink | الصلاحية |
|---|---|---|
Connect AddArtifacts باسم archives/<file>#../<outside> | SparkContext.addFile dest new File(root, uri.getFragment) ثم Utils.unpack (SparkContext.scala:1891-1900) | السلامة (مُثبت مختبريًا): كتابة أعضاء الأرشيف بصلاحية Spark UID خارج SparkFiles الخاص بالجلسة. الـ dest المختبري canonical /lab/oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS، uid 1000، dest-outside=yes. |
| نفس الـ dest، قبل فك الضغط | Utils.deleteRecursively(dest) (SparkContext.scala:1899) | التوافر (مُثبت مختبريًا): اختفاء canary الـ dest. المختبر deleteRecursively=yes؛ لم ينجُ canary.txt تحت الـ dest. أما الشقيق /lab/oracle/canary-keep.txt فقد بقي. |
| نفس الـ primitive على كل executor يجلب الأرشيف | Executor dest new File(root, sourceURI.getFragment) ثم deleteRecursively + unpack (Executor.scala:1468-1477) | السلامة / التوافر (مُستنتج): نفس بناء الـ dest على الـ executors. المختبر هو gadget جهة الـ driver. |
اسم الـ Artifact files/../../... أو archives/../../... | ArtifactUtils.concatenatePaths (ArtifactUtils.scala:26-39) | مغلق (مُثبت مختبريًا): INVALID_ARTIFACT_PATH. |
fragment مطلق على Unix /abs | new File(root, "/abs") | مغلق على Unix (مُثبت مختبريًا): لا يُتجاهل الـ parent؛ الـ dest يُدمج تحت root. الـ dest المختبري /lab/root/lab/oracle/abs-...، outside=false، canary الـ abs بقي. |
إضافة jars/ / classes/ للجلسة نفسها | session URLClassLoader | مغلق كبقية: تحميل JAR للجلسة نفسها مقصود بالتصميم. ليس هذا الاكتشاف. |
| تحميل jar عبر الجلسات | Connect isolation spark.sql.artifact.isolation.enabled=true | مغلق: الجلسات المعزولة تقرأ addedJars(sessionUUID). |
مُثبت مختبريًا مقابل مُستنتج: dest الـ hash-fragment خارج SparkFiles، حذف canary الـ dest، بقاء canary الشقيق، رفض دمج اسم files/ وarchives/، fragment مطلق على Unix يُدمج تحت root، uid 1000، فك ضغط zip محتوى داخل الـ dest: مختبر. جلب الـ executor لنفس الـ dest: مُستنتج (نفس الـ constructor). الكتابة فوق class يُحمَّل لاحقًا على classpath الـ driver: سلسلة مُستنتجة، لم تُبرهن، ولم تُضخَّم إلى RCE.
يتطلب: عميل Connect قادر على AddArtifacts مع archive=True. PySpark الرسمي spark.addArtifact(..., archive=True) يضع بالفعل fragment URI غير فارغ في الاسم (python/pyspark/sql/connect/client/artifact.py:223-236). Connect الافتراضي بلا PSK.
| السطح | المختبر |
|---|---|
اسم الـ Artifact .. / مطلق عبر concatenatePaths | files-concat=rejected INVALID_ARTIFACT_PATH |
اسم archives/ يتجاوز archives/../../... | نفس INVALID_ARTIFACT_PATH |
Unix new File(root, "/abs") | الـ dest تحت root؛ canary الـ abs oracle بقي |
| zip-slip لعضو zip تحت dest محتوى | الـ gadget يرفض الأعضاء التي تخرج من الـ dest (فئة HADOOP-13649). ليس هذه البقية. |
دمج مسار files/ (بلا تقسيم هاش) | لا يُؤخذ الـ fragment؛ الاسم لا يزال عبر concatenatePaths |
| عزل jar عبر الجلسات | مفعّل افتراضيًا لـ Connect |
| SQL / Python بلا مصادقة على Connect مفتوح | إرسال مهمة بالتصميم (SECURITY.md FAQ). ليس هذه البقية. |
| إرسال مهمة / تحميل نموذج ML | بالتصميم |
StagedArtifact يقسّم فقط الأسماء التي تبدأ بـ archives مع File.separator الخاص بالخادم، وفقط على #:
val splits = name.split("#")
if (splits.length == 2) {
(splits(0), Some(splits(1)))
}
splits(0) يُجهَّز عبر concatenatePaths. وsplits(1) يُحتفظ به كـ fragment. يربط ArtifactManager ذلك الـ fragment بـ URI الخاص بـ file-server للجلسة ويستدعي SparkContext.addArchive. الـ dest هو new File(root, uri.getFragment). تختبر SPARK-33530 sc.addArchive(s"${jarFile.getAbsolutePath}#foo") كاسم دليل تحت SparkFiles.
ادّعت الجولة الأولى من البحث أن fragment مطلقًا على Unix يتجاهل root (تجاهل parent في java.io.File للأبناء المطلقين). على Unix، new File(root, "/abs") يزيل الشرطة المائلة البادئة ويُدمج تحت root. الـ parent يُتجاهل فقط على Windows. مسار High على هذا التثبيت هو #../.
cd lab
./run.sh
Loopback 127.0.0.1:18930. مشروع Compose spark-connect-archive-fragment. الصورة eclipse-temurin:21-jdk-jammy، uid وقت التشغيل 1000. الشاهد 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. ATTACK: archives/payload-frag.zip#../oracle/SPARK-CONNECT-ARCHIVE-FRAGMENT-WITNESS الـ dest canonical /lab/oracle/...، حُذف الـ canary، كُتب pwned.txt بصلاحية uid 1000. fragment مطلق على Unix يُدمج تحت root. الـ stack يُفكَّك عند EXIT.
مرّر fragment الأرشيف عبر نفس فحص البقاء تحت root المطبَّق على اسم الـ artifact (concatenatePaths، أو ارفض .. / المطلق). أبقِ SPARK-33530 #name كمكوّن دليل واحد تحت SparkFiles. لا تنفّذ deleteRecursively على dest فشل في ذلك الفحص. الـ executors تستخدم نفس الـ constructor؛ أصلح كليهما.