
Apache Spark 4.2.0 - عالي - عن بُعد - قراءة ملف - History zip يتبع رابطًا رمزيًا مزروعًا
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · spark-history-zip-symlink
الفئة: قراءة ملف الوصول: عن بُعد
Apache Spark 4.2.0 (32f72996) - Apache Software Foundation
قام CVE-2025-54920 بإغلاق Jackson في History Server بحيث لا يُنشئ sparkEventFromJson سوى فئات فرعية من SparkListenerEvent. ومع ذلك، لا يزال zipEventLogFiles المتدحرج يفرغ كل ابن listStatus عبر FileSystem.open. يتبع LocalFileSystem رابطًا رمزيًا POSIX. يُحزم ابن مزروع في eventlog_v2_* داخل GET /api/v1/applications/{id}/logs غير المصادق عليه. جسم عضو الـ zip هو هدف SHS-UID. اسم المدخل هو الاسم الأساسي للرابط الرمزي.
هذا ليس إعادة تقديم لـ Jackson RCE. يبقى Jackson مغلقًا. هذا ليس RCE على المضيف. السلامة والتوافر معدومان.
| المعرّف | لا يوجد CVE بعد |
| الفئة | قراءة ملف (بقايا zip المتدحرج من نموذج الضحية CVE-2025-54920) |
| الوصول | عن بُعد (كاتب دليل سجل History بالإضافة إلى GET غير مصادق عليه للـ zip) |
| CWE | CWE-59، CWE-22 |
| CVSS | مرتفع: 7.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| المنتج | Apache Spark |
| المتأثر | 4.2.0 وسم v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) |
| المصادقة | PR:L = الكتابة إلى spark.history.fs.logDirectory (نفس الضحية كما في 54920). قوائم ACL لواجهة History معطّلة افتراضيًا. |
| الترخيص | GNU Affero GPL v3.0 |
| المختبر | 127.0.0.1:18980 فقط |
من يستطيع الكتابة إلى spark.history.fs.logDirectory (الافتراضي file:/tmp/spark-events) يمكنه زرع دليل متدحرج سيقوم History Server بضغطه. كتّاب التدحرج مفعّلون افتراضيًا. قوائم ACL لواجهة History معطّلة افتراضيًا. عملية SHS UID هي النائب المُلبَّس.
| الصلاحية | الطريقة / المصب | مُثبت مختبريًا أو مُستنتج |
|---|---|---|
| السرية: قراءة ملف مضيف يمكن لـ SHS UID فتحه | ازرع eventlog_v2_<id>/ مع appstatus_* صالح بالإضافة إلى JSON events_* (SparkListenerLogStart / SparkListenerApplicationStart) بالإضافة إلى ابن رابط رمزي يقع هدفه خارج دليل السجل. انتظر الفحص. GET /api/v1/applications/{id}/logs غير المصادق عليه (OneApplicationResource.getEventLogs -> FsHistoryProvider.writeEventLogs -> RollingEventLogFilesFileReader.zipEventLogFiles -> FileSystem.open) | مُثبت مختبريًا. جسم عضو الـ zip eventlog_v2_app-2026-0001/planted-secret هو SPARK-HISTORY-ZIP-SYMLINK-WITNESS. عضو الأحداث لا يحتوي على شاهد. يبقى اسم المدخل هو الاسم الأساسي للرابط الرمزي، وليس مسار الهدف. |
السرية: سجل أحداث مستأجر شقيق لا يستطيع المهاجم open عليه (660) | نفس الـ zip، هدف الرابط الرمزي هو سجل تطبيق آخر | مُستنتج. نفس FileSystem.open. لم يُختبر. |
السرية: keytab، spark-env.sh، /etc/passwd إذا كان SHS قادرًا على قراءتها | نفس الـ zip | مُستنتج. الأوراكل هو /secret/host-secret مخصص، وليس تلك الملفات. |
| سلامة ملفات SHS أو المضيف | يُقرأ الـ zip في جسم HTTP | مغلق. مُثبت مختبريًا: لا شيء. |
| توافر ملفات SHS أو المضيف | الـ zip لا يحذف أو يقتطع الهدف | مغلق. مُثبت مختبريًا: لا شيء. |
| RCE على المضيف / إنشاء gadget لـ Jackson | JsonProtocol.sparkEventFromJson | مغلق. SparkListenerEvent مطلوب. هذا المختبر يزرع فقط JSON أحداث حميدة. |
Zip slip لـ ../ في اسم المدخل | Path.getName() | مغلق. اسم ملف POSIX لا يمكن أن يحتوي على /. المدخل هو eventlog_v2_<id>/<basename>. |
هروب سلسلة المسار لـ resolveLogPath | new Path(logDir, attempt.logPath) | مغلق. يبقى كائن Path تحت دليل السجل المُهيأ. الهروب هو الـ inode. |
| إعادة كتابة الضغط / حذف المنظّف للهدف | spark.history.fs.eventLog.rolling.maxFilesToRetain الافتراضي Integer.MAX_VALUE؛ المنظّف معطّل افتراضيًا | مغلق. fs.delete المحلي لرابط رمزي يفصل الرابط، وليس الهدف. |
| إرسال مهمة غير مصادق عليه / تحميل نموذج ML | SECURITY.md FAQ / docs/ml-security.md | مغلق. بحكم التصميم، وليس هذه البقايا. |
ثبّتت apache/spark v4.2.0 (32f7299601108917fb01920a54e084595b7b3bf8) بدلًا من master أو 4.3 RC. ثمانية وعشرون بحثًا. Jackson History RCE مغلق على هذا الإصدار (JsonProtocol.scala:965-972). كان History Server لا يزال يشير إلى دليل سجل قابل للكتابة من المهاجم، وهو نفس نموذج الضحية كما في CVE-2025-54920.
يقوم zipEventLogFiles بضغط كل files، وليس فقط ملفات الأحداث:
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 الرمزية. يعلّق getLogDirInfo "handles symbolic links properly" ثم يمشي على آباء Path غير المحلولة للحصول على اسم عرض.
المنعطفات الخاطئة المسجلة بالفعل: التعامل مع SPARK-52381 على أنه قصة History بأكملها؛ زرع الشاهد في events_* (يفشل المختبر في ذلك)؛ توقع أن يحمل مدخل الـ zip مسار الهدف؛ التعامل مع دمج البادئة في resolveLogPath على أنه الهروب؛ تجربة فئة حدث HiveConnection على هذا الإصدار.
python3 spark-history-zip-symlink-Abraxas-Labs.py
أو
cd lab
./run.sh
gadget من تفريغ الـ zip المتدحرج في EventLogFileReaders.scala، وليس History Server كاملًا وليس صورة spark:4.2.0 الرسمية. لا يحلل JSON الأحداث عبر Jackson. Loopback 127.0.0.1:18980. مشروع Compose spark-history-zip-symlink. يحتوي الدليل المتدحرج على appstatus_* + events_* صالحين (بدون شاهد) بالإضافة إلى planted-secret -> /secret/host-secret.
SUCCESS SPARK-HISTORY-ZIP-SYMLINK SPARK-HISTORY-ZIP-SYMLINK-WITNESS
احزم فقط أبناء events_* / appstatus_*، وليس كل اسم listStatus. افتح بدون متابعة (O_NOFOLLOW / خيارات Hadoop لمنع الروابط الرمزية) وارفض أي ابن يقع realpath الخاص به خارج spark.history.fs.logDirectory. مساعد اسم العرض ليس فحص realpath حقيقيًا.
32f7299601108917fb01920a54e084595b7b3bf8)EventLogFileReaders.scala (addFileAsZipEntry ~48-56، zipEventLogFiles ~267-272)FsHistoryProvider.scala (writeEventLogs ~831-856، resolveLogPath ~866-896)OneApplicationResource.scala (GET .../logs ~128-165)JsonProtocol.scala (sparkEventFromJson ~965-972، قفل Jackson)