WP Meteor Website Speed Optimization Addon <= 3.4.16 - Unauthenticated Stored Cross-Site Scripting via Comment
Description
The WP Meteor Website Speed Optimization Addon plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'frontend_rewrite' function's 'WPMETEOR[N]WPMETEOR' placeholder content in all versions up to, and including, 3.4.16 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v3.4.17
Source Code
WordPress.org SVN# Research Plan: WP Meteor Unauthenticated Stored XSS via Comment (CVE-2026-2902) ## 1. Vulnerability Summary The **WP Meteor Website Speed Optimization Addon** (versions <= 3.4.16) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists in the `frontend_rewrite…
Show full research plan
Research Plan: WP Meteor Unauthenticated Stored XSS via Comment (CVE-2026-2902)
1. Vulnerability Summary
The WP Meteor Website Speed Optimization Addon (versions <= 3.4.16) is vulnerable to Unauthenticated Stored Cross-Site Scripting (XSS). The vulnerability exists in the frontend_rewrite function within the UltimateReorder class. The plugin processes the entire HTML output buffer to "delay" scripts by replacing their content with a placeholder string: WPMETEOR[N]WPMETEOR.
The core issue is that the plugin's regex-based parser identifies <script> tags without verifying their context (e.g., whether they are inside a <code> tag, a <textarea>, or an HTML comment). An unauthenticated attacker can post a comment containing a payload that bypasses WordPress's standard kses filters (which usually strip <script> tags) by using specific HTML structures that the plugin's regex misinterprets as a valid script tag. When the plugin "restores" or "processes" these placeholders, it injects malicious JavaScript into the page.
2. Attack Vector Analysis
- Endpoint:
wp-comments-post.php(Standard WordPress comment submission). - Vulnerable Parameter:
comment(The body of the comment). - Authentication: Unauthenticated (PR:N).
- Precondition: The "Ultimate" mode (Maximum available speed) must be enabled in WP Meteor
Summary
The WP Meteor plugin for WordPress is vulnerable to unauthenticated stored XSS because its script-optimization engine incorrectly identifies script tags inside HTML comments. An attacker can bypass standard comment sanitization by wrapping a malicious script in a comment block, which the plugin then extracts and executes as part of its 'delay' optimization process.
Vulnerable Code
// blocker/FirstInteraction/UltimateReorder.php lines 101-133 while (preg_match('/<script\b[^>]*?>/is', $buffer, $matches, PREG_OFFSET_CAPTURE, $searchOffset)) { $offset = $matches[0][1]; $searchOffset = $offset + 1; if (preg_match('/<\/\s*script>/is', $buffer, $endMatches, PREG_OFFSET_CAPTURE, $matches[0][1])) { $len = $endMatches[0][1] - $matches[0][1] + strlen($endMatches[0][0]); // $everything = substr($buffer, $matches[0][1], $len); $tag = $matches[0][0]; $closingTag = $endMatches[0][0]; $hasSrc = preg_match('/\s+src=/i', $tag); $hasType = preg_match('/\s+type=/i', $tag); $shouldReplace = !$hasType || preg_match('/\s+type=([\'"])((application|text)\/(javascript|ecmascript|html|template)|module)\1/i', $tag); $noOptimize = preg_match('/data-wpmeteor-nooptimize="true"/i', $tag); if ($shouldReplace && !$hasSrc) { // inline script $content = substr($buffer, $matches[0][1] + strlen($matches[0][0]), $endMatches[0][1] - $matches[0][1] - strlen($matches[0][0])); if (!$noOptimize && apply_filters('wpmeteor_exclude', false, $content)) { $tag = preg_replace('/^<script\b/i', "<script {$EXTRA} data-wpmeteor-nooptimize=\"true\"", $tag); } $replacement = $tag . "WPMETEOR[" . count($REPLACEMENTS) . "]WPMETEOR" . $closingTag; $REPLACEMENTS[] = $content; $buffer = substr_replace($buffer, $replacement, $offset, $len); continue; } } }
Security Fix
@@ -1,6 +1,6 @@ { "version": 3, - "sources": ["../../../src/css/vendor/settings.css", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/settings.scss", "../../../src/css/admin/%3Cinput%20css%201RoP21%3E", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/ultimate.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/simple.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/textarea.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/regexp-textarea.scss"], + "sources": ["../../../src/css/vendor/settings.css", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/settings.scss", "../../../src/css/admin/%3Cinput%20css%20l6ll68%3E", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/ultimate.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/simple.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/textarea.scss", "../../../src/css/admin/Users/ag/development/fastpixel-servers/packages/wp-meteor/src/css/admin/components/regexp-textarea.scss"], "sourcesContent": [".ui-state-default{display:inline;border-color:#ccc;border-width:1px;border-style:solid;border-bottom-width:0px;padding:12px 0px;background:none repeat scroll 0% 0% #e4e4e4}.ui-tabs-nav{padding-bottom:9px;padding-left:10px;border-bottom:1px solid #ccc}.ui-tabs-anchor,.ui-tabs-anchor:focus{text-decoration:none;font-weight:600;font-size:15px;line-height:24px;color:#7f7f7f;padding:10px;box-shadow:none}.ui-tabs-anchor:hover,.ui-state-active a{color:black}.ui-tabs-active,.ui-tabs-active:hover,.ui-tabs-active:active{color:#000;background:#efefef !important}.metabox-holder .postbox{max-width:100%}.metabox-holder .postbox h3{padding-left:10px}.settings-tab{float:left}.right-column-settings-page{max-width:270px;float:right}\n", "\n@import '../vendor/settings.css';\n\n@keyframes opacity {\n 0% {\n opacity: 0;\n }\n\n 50% {\n opacity: 1\n }\n\n 100% {\n opacity: 0;\n }\n}\n\n#tabs .ui-state-disabled {\n opacity: 0.1;\n}\n\n#tabs {\n .ui-tabs-nav {\n display: flex;\n padding-bottom: 0px;\n\n .ui-tabs-tab {\n margin-bottom: 0px;\n }\n\n .ui-tabs-anchor, .ui-tabs-anchor:focus {\n display: block;\n width: 100%;\n height: 100%;\n }\n\n }\n\n .tab { \n display: none; \n padding: 0 20px;\n }\n\n ul.content {\n padding-inline-start: 40px;\n li {\n list-style-type: circle;\n margin-left: 8px;\n }\n }\n\n .answer {\n padding-left: 20px;\n }\n\n}\n\n.settings-tab {\n float: none;\n [dir=\"rtl\"] & {\n direction: ltr;\n }\n}\n\n#author {\n .row {\n display: flex;\n flex-direction: row;\n }\n .author-image img {\n width: 20vw;\n height: auto;\n }\n .author-bio {\n padding-left: 10px;\n width: 70vw;\n button {\n padding: 2px;\n }\n }\n ul {\n padding-inline-start: 1em;\n li {\n list-style-type: circle;\n margin-left: 1em;\n }\n }\n}\n\n@media screen and (max-width: 414px) {\n #tabs {\n .ui-tabs-nav {\n display: flex;\n \n .ui-tabs-tab {\n writing-mode: vertical-lr;\n flex-grow: 1;\n }\n\n }\n }\n #author {\n h1 {\n text-align: center;\n }\n .row {\n flex-direction: column;\n }\n .author-image img {\n width: 100%;\n }\n }\n}\n\n.tooltip {\n &.__react_component_tooltip {\n z-index: 9999;\n }\n line-height: 1.4em;\n}\n\nsection.banner {\n width: 100%;\n background-color: #fff;\n display: flex;\n align-items: center;\n border: 1px solid #ccc;\n margin-top: 35px;\n margin-bottom: 45px;\n position: relative;\n\n .image, .line, .button-wrap {\n padding: 2px 4px;\n }\n\n .image img {\n width: 120px;\n }\n\n .line {\n font-weight: 600;\n font-size: 15px;\n }\n}\n\n\n@import './components/ultimate.scss';\n@import './components/simple.scss';\n@import './components/textarea.scss';\n@import './components/regexp-textarea.scss';", ... (truncated)
Exploit Outline
To exploit this vulnerability, an unauthenticated attacker needs the plugin to be configured in 'Ultimate' (Maximum available speed) mode. The attacker submits a public WordPress comment using a payload like '<!-- <script>alert(1)</script> -->'. Because WordPress allows HTML comments, this bypasses standard script-stripping filters (kses). When the plugin generates the page for a visitor, the 'frontend_rewrite' function parses the entire HTML buffer using a regular expression that matches script tags regardless of their context (e.g., within comments). The plugin extracts the JavaScript inside the comment, stores it in an internal execution queue ('REPLACEMENTS'), and replaces the tag with a placeholder. The plugin's frontend JavaScript engine then restores and executes the stored scripts, causing the malicious payload to run in the victim's browser context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.