CVSS 9.8 なのに「たいてい刺さらない」— Joomla CVE-2026-73373 の成立条件を機械で判定する
Joomla! Core に、危険なファイル拡張子の既定リストに SHTML が含まれていなかったという脆弱性が公開されている。CVE-2026-73373 である。
| 項目 | 値 |
|---|---|
| CVE | CVE-2026-73373 |
| NVD 公開 | 2026-08-18(最終更新 2026-09-03) |
| CVSS | 9.8(CVSS 3.1・NVD Primary・AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-434(危険な種類のファイルの無制限アップロード) |
| KEV | 2026-09-12 時点で未収載(CISA KEV カタログ 2026.09.11 で確認) |
| 対象 | Joomla 1.0.0 以上 5.4.8 未満、および 6.0.0 以上 6.1.3 未満 |
| アドバイザリ | Joomla Security Centre [20260810] |
CVSS は 9.8 である。ところが公式アドバイザリの文言には条件が付いている。
The default list of dangerous files did not include SHTML files. On servers that executed these files, that could lead to code execution.
「On servers that executed these files(これらのファイルを実行するサーバでは)」 の一句がこの CVE の性格を決めている。つまり 9.8 は「SHTML を実行する構成だった場合」の値であって、自分の環境でいくらの深刻度になるかは、Joomla ではなく Web サーバ側の設定で決まる。
この記事で扱うのは3つである。①なぜ SHTML だと実行に届くのか、②なぜ CVSS 9.8 をそのまま自分の優先度にしてはいけないのか、③「該当版か」と「SHTML を実行する設定か」の2条件を人が目で確かめると漏れるので、それを Node.js のスクリプトに落とす。攻撃コードは書かない。
1. SHTML が実行に届く仕組み
.shtml は Server Side Includes(SSI) のファイルである。SSI は CGI より古い仕組みで、HTML の中に書いたコメント形式の命令をサーバ側が展開して返す。
1 | |
1行目はほかのファイルを埋め込む命令、2行目はコマンドを実行してその出力を埋め込む命令である。#exec が有効な構成では、これはそのまま任意コマンド実行になる。
Apache でこれが効くには、次の3つがそろう必要がある。
mod_includeが読み込まれている(LoadModule include_module ...)- 対象ディレクトリで
Options +Includes(または+IncludesNOEXEC)が有効 .shtmlに出力フィルタが割り当たっている(AddOutputFilter INCLUDES .shtml)
3つそろわなければ、アップロードされた .shtml は単なる静的ファイルとして返るだけである。nginx + PHP-FPM の構成なら、ssi on; を書いていない限り SSI は解釈されない。
つまり Joomla 側の欠陥は「危険リストに載せ忘れた」という判定漏れそのもので、そこからコード実行まで届くかどうかはサーバ設定という別の要素が握っている。
それでも更新する理由
「うちは SSI を切っているから関係ない」で終わらせない理由が2つある。
1つ目。リストの穴は環境をまたぐ。Joomla のファイルを丸ごと別サーバへ移す、あるいは共用ホスティングの設定が変わると、同じファイル群の上で条件がそろう。判定漏れを塞いでおけば、サーバ設定の変化に依存しなくなる。
2つ目。CVSS の PR:N(事前の権限不要)を軽く見ないほうがよい。Joomla のメディアマネージャは権限を要求するが、フォーム系や投稿系の拡張機能で未認証アップロードを許している構成は珍しくない。その経路では権限なしで到達しうる。
該当範囲が 1.0.0 からという点も特徴である。この判定漏れが長期にわたって存在していたということで、古い版を動かし続けているサイトはすべて対象に入る。
2. CVSS 9.8 を自分の優先度にそのまま使わない
ここが実務の分かれ目である。CVSS 基本値は環境条件を織り込まない最悪値として付く。CVE-2026-73373 の 9.8 は「SHTML を実行するサーバで、未認証のアップロード経路がある場合」を前提にした数字である。
だから、やるべきことはこの順になる。
- 自分の Joomla が該当版かを版数で確かめる(5.4.8 未満 / 6.1.3 未満)
- 自分のサーバが SHTML を実行するかを設定で確かめる
- 2 が成立していれば即時、していなければ通常の更新サイクルで上げる
1 と 2 は独立しているので、どちらか片方だけ見ても優先度は決まらない。そして両方を人が目で追うと、設定ファイルが include で分かれている環境では必ず取りこぼす。機械でやるべき部分である。
3. 2条件を判定するスクリプト
読み取りだけを行う。攻撃も設定変更もしない。
1 | |
ssi-exposure.js として保存し、Joomla のルートを渡す。
1 | |
実際に走らせた出力
手元の macOS(Apache の設定は同梱されているが SSI は有効でない)で走らせた結果である。Joomla を置いていないディレクトリを渡した。
1 | |
この出力が示しているのは、目で追うと誤判定する例そのものである。 Options IncludesNoExec が2箇所で見つかっている。「Includes」の文字列を grep しただけなら「SSI が有効だ」と読んでしまう。ところが LoadModule include_module は1件も無い。モジュールが読み込まれていないので、この Options は効かない。だからスクリプトは server_executes_shtml: false を返している。
判定を includesModule.length > 0 && (...) という AND にしてあるのはこのためである。設定ファイルの文字列だけを数える検査は、この種の「書いてあるが効いていない」行で誤検知する。
版数判定の動作確認
境界の3点で確かめた。libraries/src/Version.php を書き換えて走らせた実測である。
| Version.php の値 | joomla_vulnerable |
verdict |
|---|---|---|
| 5.4.7 | true |
MEDIUM: 該当版だが SHTML を実行する設定は見つからない |
| 5.4.8 | false |
OK: 修正版(5.4.8 以降 / 6.1.3 以降) |
| 6.1.2 | true |
MEDIUM: 該当版だが SHTML を実行する設定は見つからない |
5.4.7 と 5.4.8 のあいだ、6.1.2 と 6.1.3 のあいだで反転している。NVD の CPE(versionEndExcluding が 5.4.8 と 6.1.3)どおりの境界である。
文字列比較にしなかった理由
isVulnerable でわざわざ major * 1e6 + minor * 1e3 + patch に直しているのは、バージョンを文字列で比べると壊れるからである。実測する。
1 | |
1 | |
文字列比較では 5.4.10 が 5.4.8 より小さい(=古い)と判定される。 辞書順で 1 が 8 より前に来るためである。パッチ番号が2桁に入った時点で、修正済みのサイトが「該当版」として報告されるようになる。数値に直した側では期待どおり false になる。
1 | |
1 | |
1e3 の桁を選んでいるのは、minor と patch が3桁(999)まで入る前提で桁が衝突しないようにするためである。Joomla の実際の番号はこれよりはるかに小さいので、余裕がある。
4. 対応の手順
判定が出たら、次の順に動かす。
恒久対応は修正版へ上げること。 5.x 系なら 5.4.8 以降、6.x 系なら 6.1.3 以降である。管理画面のシステム → 更新 → Joomla の更新から上げられる。5 から 6 へのメジャー更新はこの CVE の対応としては不要で、自分の系列の最新パッチ版に上げるのが最短である。
すぐに上げられない場合の緩和は、サーバ側で SHTML を止めることである。 Apache なら Joomla のアップロード先に対して次を明示する。
1 | |
nginx なら1行で足りる。
1 | |
Joomla 側でメディアマネージャの許可拡張子から shtml を外すだけでは足りない場合がある。 判定漏れは「危険リスト」側にあり、許可リストを参照しない経路(拡張機能のアップロード処理)が残るからである。サーバ側で止めるほうが確実である。
更新後はアップロード先を実ファイルで確認する。 スクリプトの strayShtml が images/ media/ tmp/ を走査するのはこのためである。見つかったら中身の <!--#exec / <!--#include を確認し、アクセスログでその URL への到達があったかを突き合わせる。到達があったなら、コード実行を前提に管理者アカウント・拡張機能の追加・データベース内容の点検まで広げる。
版の確認は管理画面の表示だけに頼らないほうがよい。 スクリプトが libraries/src/Version.php を直接読んでいるのは、更新が実ファイルに届いているかを確かめるためである。管理画面のバージョン表示はキャッシュの影響を受けることがある。
まとめ
- CVE-2026-73373 は Joomla の危険拡張子リストに SHTML が無かったという判定漏れで、対象は 1.0.0 以上 5.4.8 未満 / 6.0.0 以上 6.1.3 未満
- CVSS 9.8 は最悪値であり、コード実行に届くかは Web サーバが SSI を実行するかという別条件で決まる
- Apache で SSI が効くには
mod_includeの読み込みが必要。**Options Includesが書いてあるだけでは効かない**(手元の実測でこの状態が出た) - バージョン比較を文字列でやると 5.4.10 が 5.4.8 より古いと判定される。数値に直してから比べる
- 条件が成立していなくても更新はする。判定漏れそのものは環境の変化で刺さるようになる
この CVE の詳細は CVE Watch の CVE-2026-73373 にも掲載している。クラウド各社(AWS / GCP / Azure)と Linux ディストリの対応状況を並べて整理してある。
一次情報は NVD の CVE-2026-73373 と Joomla Security Centre のアドバイザリ 20260810 である。本記事の数値・版数はこの2本と CISA KEV カタログ(2026.09.11)で確認した(確認日 2026-09-12)。