
Apache Spark 4.2.0 - عالي - عن بُعد - كتابة ملف - تطبيق History appStorePath معرّف التطبيق غير المعقّم
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-store-path
الفئة: كتابة ملف (مشروطة بالإعدادات) الوصول: عن بُعد
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
HistoryServerDiskManager.appStorePath ينفّذ new File(appStoreDir, appId + extension) دون أي تعقيم. Utils.sanitizeDirName مخصّص فقط لأسماء ملفات سجل الأحداث. حقل JSON App ID يُقرأ عبر asText(). التبويب يخزّن event.appId.orNull. Lease.commit يستخدم تلك السلسلة. معرّف التطبيق ../ يكتب ملف .rdb خارج spark.history.store.path. القيمة الافتراضية غير المضبوطة لـ store.path تكون في الذاكرة (بلا كتابة). المحتويات هي ملف مخزن، وليست تنفيذًا عن بُعد للأوامر.
| المعرّف | لا يوجد CVE بعد |
| الفئة | كتابة ملف (مخزن قرص History مشروط بالإعدادات) |
| الوصول | عن بُعد (كاتب سجل الأحداث بالإضافة إلى History مع ضبط spark.history.store.path؛ قوائم التحكم في واجهة المستخدم معطّلة افتراضيًا) |
| CWE | CWE-22 |
| CVSS | مرتفع: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N عند ضبط spark.history.store.path |
| المنتج | Apache Spark History Server |
| المتأثر | 4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| المصادقة | كاتب spark.history.fs.logDirectory (الافتراضي file:/tmp/spark-events)؛ يجب أن يكون spark.history.store.path مضبوطًا في History |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1:18990 فقط. أداة الربط المذكورة. شاهد .rdb خارج store.path. |
نفس نموذج الضحية كما في CVE-2025-54920: History Server موجّه إلى دليل سجلات يستطيع المهاجم الكتابة فيه. لم يعد Jackson ينشئ أدوات من فئة HiveConnection. مسار مخزن القرص لا يزال يثق بمعرّف التطبيق في JSON الحدث.
| الطريقة | نقطة الوصول | الصلاحية | المختبر مقابل الاستنتاج |
|---|---|---|---|
زرع سجل حدث يكون فيه App ID في JSON مساويًا ../<name> (أو ../../oracle/...). انتظار الفحص. طلب GET يحمّل واجهة التطبيق / getAppUI / loadDiskStore / createDiskStore | HistoryServerDiskManager.appStorePath ثم Lease.commit renameTo | السلامة: إنشاء مسار .rdb (RocksDB افتراضيًا) أو .ldb (LevelDB) خارج {store.path}/apps بصلاحية History UID. مُثبت مختبريًا على الأداة: escaped=yes، الملف /work/oracle/SPARK-HISTORY-STORE-PATH-WITNESS.rdb يحتوي على الشاهد. المحتويات هي ملف مخزن، وليست بايت كود مضيف يختاره المهاجم، وليست صدفة أوامر. | مُثبت مختبريًا (كتابة الأداة لبايتات الشاهد في المسار المُهرَّب). renameTo الكامل لـ History على دليل RocksDB هو نفس الربط. |
| نفس معرّف التطبيق عندما تكون الوجهة المُهرَّبة دليلًا موجودًا بالفعل | Lease.commit if (dst.isDirectory()) deleteStore(dst) ثم Utils.deleteRecursively | التوافر: حذف دليل موجود ينتهي اسمه بالفعل بلاحقة الواجهة الخلفية، ثم استبداله بالمخزن المستأجر. | استنتاجي (تثبيت HistoryServerDiskManager.scala commit). الأداة كتبت ملفًا، ولم تستدعِ deleteRecursively. |
القيمة الافتراضية غير المضبوطة لـ spark.history.store.path | LOCAL_STORE_DIR createOptional؛ diskManager = storePath.map | مغلق. تبويب في الذاكرة. لا يوجد HistoryServerDiskManager. مُثبت مختبريًا gated=yes written=no. | مُثبت مختبريًا |
تطبيق Utils.sanitizeDirName على معرّف التطبيق أولًا | مساعد اسم ملف سجل الأحداث | يبقى داخل apps/. يتحول / و : إلى -. مُثبت مختبريًا سلبيًا. | مُثبت مختبريًا |
التحكم بمعرّف التطبيق local-control مع ضبط store.path | نفس الربط | يكتب {store.path}/apps/local-control.rdb. يبقى داخل apps/. | مُثبت مختبريًا |
مغلق مجاور: المخزن الافتراضي غير المضبوط (في الذاكرة). Jackson sparkEventFromJson لا يزال يتطلب SparkListenerEvent (CVE-2025-54920). احتياطي URL getAppUI eventlog_v2_ + nameForAppAndAttempt مُعقَّم. مفتاح KV في التبويب ليس مسار نظام ملفات. مخطط HybridStore / kvstore لا يحتوي على Id.CLASS. History HTTP لا يقبل مسار رفع. الضغط الافتراضي معطّل. المنظّف الافتراضي معطّل. إرسال المهام على عنقود مفتوح (SECURITY.md FAQ). تحميل نموذج ML (docs/ml-security.md). هذه البقايا ليست تنفيذًا عن بُعد للأوامر على المضيف.
ثبّتُ Spark 4.2.0، ليس master، وليس 4.3 RC. ثمانية وعشرون بحثًا. ثغرة Jackson RCE العامة في History مغلقة في هذه الطبقة. كتّاب سجل الأحداث يستدعون بالفعل Utils.sanitizeDirName لذا لا يمكن أن يحتوي ملف السجل على /. اسم ملف ذاكرة القرص المؤقتة يستخدم معرّف التطبيق الخام.
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. في Unix، new File(parent, "../../oracle/name.rdb") يُحلّ خارج parent. JsonProtocol.applicationStartFromJson يأخذ json.get("App ID").asText(). AppListingListener.onApplicationStart يخزّن event.appId.orNull. lease.commit(appId, attempt.info.attemptId) يغذّي تلك السلسلة إلى appStorePath.
مسارات خاطئة مسجّلة بالفعل: اعتبار store.path الافتراضي غير المضبوط كتابةً (إنه في الذاكرة)؛ توقّع أن new File(dir, "/abs") في Unix سيتجاهل parent (لا يفعل؛ البقايا هي ..)؛ ادّعاء RCE لأن اللاحقة .rdb؛ تشغيل History Server كامل بينما الربط هو الأداة؛ اعتبار sanitizeDirName تغطي هذا المسار.
python3 spark-history-store-path-Abraxas-Labs.py
أو
cd lab
./run.sh
أداة HistoryServerDiskManager.appStorePath على 127.0.0.1:18990. مقتطفات التثبيت تحت lab/pin/. لا يتم تشغيل History Server كامل. اربطه بواجهة 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
شغّل Utils.sanitizeDirName (أو ارفض .. / / / NUL) على appId و attemptId داخل appStorePath قبل new File. بعد الربط، اشترط أن يبقى dst.getCanonicalFile() تحت appStoreDir.getCanonicalFile(). إلغاء ضبط spark.history.store.path يبقى الافتراضي الآمن.
32f7299601108917fb01920a54e084595b7b3bf8)HistoryServerDiskManager.scala appStorePath / Lease.commitFsHistoryProvider.scala diskManager / loadDiskStore / onApplicationStartJsonProtocol.scala applicationStartFromJsonUtils.scala sanitizeDirNameHistory.scala LOCAL_STORE_DIR (اختياري)