CVE-2026-2902

WP Meteor Website Speed Optimization Addon <= 3.4.16 - Unauthenticated Stored Cross-Site Scripting via Comment

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
3.4.17
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.4.16
PublishedApril 28, 2026
Last updatedApril 29, 2026
Affected pluginwp-meteor

What Changed in the Fix

Changes introduced in v3.4.17

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/wp-meteor/3.4.16/assets/css/admin/settings.css.map /home/deploy/wp-safety.org/data/plugin-versions/wp-meteor/3.4.17/assets/css/admin/settings.css.map
--- /home/deploy/wp-safety.org/data/plugin-versions/wp-meteor/3.4.16/assets/css/admin/settings.css.map	2025-04-08 17:54:00.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-meteor/3.4.17/assets/css/admin/settings.css.map	2026-02-21 17:46:48.000000000 +0000
@@ -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.