CVE-2026-3620

Word Replacer <= 0.4 - Authenticated (Administrator+) Stored Cross-Site Scripting via 'Replacement' Parameter

mediumImproper Input Validation
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Word Replacer plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'replacement' parameter in all versions up to, and including, 0.4. This is due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Administrator-level access and above, 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:H/PR:H/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
High
Privileges Required
High
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=0.4
PublishedJune 1, 2026
Last updatedJune 2, 2026
Affected pluginword-replacer
Research Plan
Unverified

This research plan targets CVE-2026-3620, a Stored Cross-Site Scripting (XSS) vulnerability in the **Word Replacer** plugin (<= 0.4). ### 1. Vulnerability Summary The Word Replacer plugin allows administrators to define a list of words or phrases to be automatically replaced across the site's conte…

Show full research plan

This research plan targets CVE-2026-3620, a Stored Cross-Site Scripting (XSS) vulnerability in the Word Replacer plugin (<= 0.4).

1. Vulnerability Summary

The Word Replacer plugin allows administrators to define a list of words or phrases to be automatically replaced across the site's content. The vulnerability exists because the replacement parameter, which defines the new string to be inserted, is neither sanitized when saved to the database nor properly escaped when rendered on the frontend. Since the replacement occurs via content filters (like the_content), the injected script executes in the context of any user viewing a page where the replacement has been triggered.

2. Attack Vector Analysis

  • Endpoint: Admin settings page, typically found at wp-admin/options-general.php?page=word-replacer or wp-admin/admin.php?page=word-replacer.
  • Vulnerable Parameter: replacement (likely part of an array, e.g., replacement[] or a field named word_replacer_replacement).
  • Authentication: Administrator-level privileges are required.
  • Impact: Stored XSS. An attacker with admin access (or via CSRF if nonces are missing) can inject scripts that execute for all site visitors, including other administrators, potentially leading to session hijacking or further site compromise.

3. Code Flow (Inferred)

  1. Registration: The plugin registers an admin page via add_options_page() or add_menu_page() during the admin_menu hook.
  2. Data Ingestion: When the admin saves settings, the plugin processes $_POST. It likely uses update_option() to store pairs of "original" and "replacement" strings.
  3. Missing Sanitization: Before update_option(), the code fails to apply wp_kses() or sanitize_text_field() to the replacement string.
  4. Content Filtering: The plugin registers a filter on the_content (and possibly the_title or comment_text):
    add_filter('the_content', 'word_replacer_filter');
  5. Execution Sink: Inside the filter, the plugin performs a replacement (likely using str_replace or preg_replace).
  6. Missing Output Escaping: The result of the replacement is returned directly to the content stream without being passed through esc_html() or similar functions, allowing the browser to execute the injected HTML/JS.

4. Nonce Acquisition Strategy

Since the attacker must be an Administrator, the easiest way to handle nonces is to use the browser_eval tool to extract them from the settings form.

  1. Navigate: Use browser_navigate to go to the Word Replacer settings page: /wp-admin/options-general.php?page=word-replacer.
  2. Identify Nonce: The settings form will likely contain a hidden input field for the nonce.
  3. Extract:
    // Inferred nonce field names
    browser_eval("document.querySelector('input[name=\"_wpnonce\"]')?.value || document.querySelector('input[name=\"word_replacer_nonce\"]')?.value")
    
  4. Bypass Check: If the plugin uses the WordPress Settings API (register_setting), the nonce is handled automatically by options.php. If it uses a custom POST handler, check if check_admin_referer() is actually called.

5. Exploitation Strategy

The goal is to save a replacement rule where the replacement string contains an XSS payload.

Step 1: Discover Form Structure
Navigate to the settings page and inspect the input names for "Original Word" and "Replacement Word".

  • Assumption: Original field is original[] and replacement is replacement[].

Step 2: Submit Payload
Submit a POST request to the settings handler (either options.php or the settings page itself).

  • URL: http://localhost:8080/wp-admin/options.php (if using Settings API) or http://localhost:8080/wp-admin/options-general.php?page=word-replacer.
  • Method: POST
  • Payload:
    action=update&
    option_page=word_replacer_settings&
    _wpnonce=[EXTRACTED_NONCE]&
    word_replacer_original[0]=VULN_CHECK&
    word_replacer_replacement[0]=<script>alert(document.domain)</script>VULN_CHECK
    

Step 3: Trigger Execution
Create a public post containing the string VULN_CHECK.

6. Test Data Setup

  1. Plugin Installation: Ensure word-replacer version 0.4 is active.
  2. Administrative User: Login as admin.
  3. Target Content:
    wp post create --post_title='XSS Trigger Page' --post_content='This is a test for VULN_CHECK.' --post_status='publish'
    

7. Expected Results

  • The settings page should accept the <script> tag without stripping it.
  • When visiting the "XSS Trigger Page", the word VULN_CHECK should be preceded/replaced by the script tag.
  • The browser should execute alert(document.domain).

8. Verification Steps

  1. Database Check:
    wp option get word_replacer_settings --format=json
    # Verify the 'replacement' value contains the raw <script> tag.
    
  2. Frontend Source Check:
    Use http_request to fetch the trigger post and grep for the raw payload.
    # Replace URL with the actual post URL
    http_request "http://localhost:8080/?p=[POST_ID]" | grep "<script>alert"
    

9. Alternative Approaches

  • Attribute-based XSS: If <script> tags are somehow blocked by a firewall, try an image attribute:
    replacement = <img src=x onerror=alert(1)>
  • Admin-Dashboard XSS: Check if the replacement rules are also rendered unescaped on the plugin's own settings page (Reflected or Stored XSS in the admin UI). If the plugin displays the "Current Replacements" in a table, it might be vulnerable there too:
    original = normal_word
    replacement = </td><script>alert(1)</script><td> (Attempting to break out of a table cell).

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.