
Proof-of-concept for authenticated stored XSS in Autoptimize < 3.1.14, exploiting insufficient attribute sanitization in image preload tag generation.
Vulnerability: Authenticated (Contributor+) Stored Cross-Site Scripting (XSS)
Affected Version: Autoptimize <= 3.1.13
Patched Version: Autoptimize 3.1.14
File: classes/autoptimizeImages.php
The vulnerability exists in the create_img_preload_tag() method within classes/autoptimizeImages.php. This function is responsible for generating <link rel="preload"> tags for images found in the content when image optimization or preloading is enabled.
In version 3.1.13, the function uses a "blacklist" approach (via preg_replace) to remove specific attributes like title, alt, , , , and from the original `` tag before converting it to a tag. However, it fails to remove event handler attributes such as or .
classidwidthheight<link>onloadonerrorAn authenticated attacker (with at least Contributor role) can embed a malicious `` tag in a post. When the plugin processes this post to generate preload links, the malicious event handler is preserved and injected into the <head> of the page inside a <link> tag. Since <link rel="preload"> supports the onload event, the JavaScript executes when the resource is loaded.
// 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/main/," sizes=', ' srcset=' );
$_to = array( '<link rel="preload" as="image" ', ' href=', ' imagesizes=', ' imagesrcset=' );
$tag = str_replace( $_from, $_to, $tag );
// INSUFFICIENT SANITIZATION: Only removes specific attributes
$tag = preg_replace( '/ ((?:title|alt|class|id|loading|fetchpriority|decoding|data-no-lazy|width|height)=".*")/Um', '', $tag );
// ...
return $tag;
}
create_img_preload_tag is often triggered for images detected in the viewport or explicitly preloaded.<img src="https://example.com/image.jpg" onload="alert('XSS_POC_SUCCESS')">
<!-- Malicious Image -->
<img src="https://raw.githubusercontent.com/ciscocamelo/cve-2025-13401-xss-stored/main/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, etc. might be stripped, but onload remains.<link> tag, preloads the resource, and fires the onload event, executing the XSS.The patch replaces the blacklist regex with wp_kses(), enabling a strict whitelist of allowed attributes for the generated <link> tag.
// 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 );