CVE-2026-3369

Better Find and Replace – AI-Powered Suggestions <= 1.7.9 - Authenticated (Author+) Stored Cross-Site Scripting via Uploaded Image Title

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

Description

The Better Find and Replace – AI-Powered Suggestions plugin for WordPress is vulnerable to Stored Cross-Site Scripting via uploaded image title in versions up to, and including, 1.7.9 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author-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:L/PR:L/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.7.9
PublishedApril 15, 2026
Last updatedApril 15, 2026

What Changed in the Fix

Changes introduced in v1.8.0

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-3369 ## 1. Vulnerability Summary The **Better Find and Replace – AI-Powered Suggestions** plugin (up to 1.7.9) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists in the plugin's media replacement interface, specifically within the…

Show full research plan

Exploitation Research Plan: CVE-2026-3369

1. Vulnerability Summary

The Better Find and Replace – AI-Powered Suggestions plugin (up to 1.7.9) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists in the plugin's media replacement interface, specifically within the assets/js/rtafar.media.replacer.min.js file. The plugin fetches attachment data (including titles) and dynamically constructs HTML strings to display search results and previews. Because it fails to sanitize or escape the title attribute of images before injecting them into the DOM using jQuery's .html() method, an authenticated user with Author-level permissions can upload an image with a malicious title to execute arbitrary JavaScript in the context of any user (typically an administrator) who uses the plugin's media management tools.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php (for searching) and wp-admin/admin.php?page=rtafar-media-replacer (the UI where XSS triggers).
  • Vulnerable Parameter: The post_title (Title) of an uploaded attachment.
  • Authentication Level: Author or higher. Authors have the upload_files and edit_posts capabilities required to upload media and modify its metadata.
  • Preconditions: The attacker must be able to upload an image and modify its title. The victim (Admin) must navigate to the plugin's "Media Replacer" page and either perform a search or initiate a replacement that renders the malicious image's title.

3. Code Flow

  1. Data Source: The attacker uploads an image. WordPress stores the title in the wp_posts table (column post_title).
  2. Data Retrieval: In the admin interface, assets/js/rtafar.media.replacer.min.js uses the query-attachments AJAX action to find images.
  3. Construction (Sink 1 - Search Results):
    • Inside setupSearchInput, the plugin receives a JSON array of attachments.
    • It iterates through the results and builds an HTML string:
      // Truncated from assets/js/rtafar.media.replacer.min.js
      return '<div class="image-item">\n <img ... alt="'.concat(e.title, '" ...>')
      
    • It then calls jQuery(".image-container").html(t), where t is the unsanitized string.
  4. Construction (Sink 2 - Preview Modal):
    • In setupReplaceButton, the plugin calls wp.media.attachment(id).fetch().
    • It uses the imagePreview function:
      imagePreview: function(e, t, a) {
          return '<img ... alt="'.concat(a, '"  />') // 'a' is the title
      }
      
    • It then calls jQuery(".old-media-preview-wrapper").html(...) with the result.

4. Nonce Acquisition Strategy

The query-attachments action is a WordPress core AJAX handler. While it usually requires a nonce, the plugin's custom media page enqueues the standard WordPress media library, which provides these nonces.

  1. Identify the Page: The media replacer tool is located at wp-admin/admin.php?page=rtafar-media-replacer (inferred from JS naming).
  2. Accessing the Page: The PoC agent should navigate to this page using browser_navigate.
  3. Extracting the Nonce: If the query-attachments call requires a nonce, it is typically stored in the wp.media configuration.
    • JavaScript Check: browser_eval("wp.media.view.settings.post.nonce").
    • Alternative: Check for a localized object like rtafar_media_replacer_vars.

5. Exploitation Strategy

  1. Upload Malicious Media:
    • Authenticate as an Author.
    • Upload a legitimate image file (e.g., image.jpg) using wp-admin/async-upload.php.
    • Capture the attachment_id from the response.
  2. Inject XSS Payload into Title:
    • Use a POST request to wp-admin/post.php to update the attachment's metadata.
    • Payload: poc" onerror="alert(document.domain)" x="
    • This payload breaks out of the alt="..." attribute.
  3. Trigger XSS (Victim Perspective):
    • Authenticate as an Administrator.
    • Navigate to wp-admin/admin.php?page=rtafar-media-replacer.
    • Locate the search input (class .input-media-replace-query).
    • Type a search string (e.g., "poc") that matches the malicious image's title.
    • The plugin will fetch the attachment and call .html(), triggering the onerror event.

6. Test Data Setup

  • User: Create a user with the author role.
  • Image: A standard JPG file named test.jpg.
  • Plugin Page: Ensure the plugin "Better Find and Replace" is active.

7. Expected Results

  • When the Administrator searches for the image in the "Media Replacer" tool, the alt attribute of the rendered <img> tag will be: alt="poc" onerror="alert(document.domain)" x="".
  • The browser will attempt to load the image, and upon the (inevitable) completion or error, the onerror handler will fire, executing alert(document.domain).

8. Verification Steps

  1. Database Check:
    • wp db query "SELECT post_title FROM wp_posts WHERE post_type='attachment' AND post_title LIKE '%onerror%'".
  2. AJAX Response Check:
    • Intercept the query-attachments response in the browser or via http_request and verify the title field contains the raw payload: "title":"poc\" onerror=\"alert(document.domain)\" x=\"".
  3. DOM Inspection:
    • Use browser_eval("document.querySelector('.image-container img')?.getAttribute('onerror')") to confirm the attribute was successfully injected into the DOM.

9. Alternative Approaches

If the search results gallery doesn't trigger immediately:

  • Preview Trigger: Click the "Replace" button (class .btn-img-replace) on any image in the search results. This triggers the imagePreview logic and setupReplaceButton logic, which also uses the unsanitized title in a .html() sink.
  • Video Vector: The code also handles video types. Try uploading a video and setting the title, as the setupSearchInput function handles videos in a similar (though slightly different) unsanitized concatenation path.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Better Find and Replace – AI-Powered Suggestions plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) in versions up to 1.7.9. This occurs because the plugin's Media Replacer tool fails to sanitize or escape image titles before injecting them into the administrative interface using jQuery's .html() method, allowing Author-level users to execute arbitrary JavaScript in an administrator's session.

Vulnerable Code

// assets/js/rtafar.media.replacer.min.js:1
if("image"===e.type){
    var t="image"===e.type?e.url:e.thumb.src;
    return'<div class="image-item">\n                                        <img class="media-item-'.concat(e.id,'" data-id="').concat(e.id,'" src="').concat(t,'" alt="').concat(e.title,'" height="250px">\n                                        <button class="button-overlay btn-img-replace" data-id="').concat(e.id,'">Replace</button>\n                                    </div>')}

---

// assets/js/rtafar.media.replacer.min.js:1
imagePreview: function(e, t, a) {
    return '<img class="media-item-'.concat(e,' preview-image" data-oldmediaid="').concat(e,'" src="').concat(t,'" alt="').concat(a,'"  />')
}

---

// assets/js/rtafar.media.replacer.min.js:1 (inside setupSearchInput)
.then((function(e){if("object"===r(e.data)&&0!==Object.keys(e.data).length){var t=e.data.map((function(e){ ... })).join("");jQuery(".image-container").html(t)}}))

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/real-time-auto-find-and-replace/1.7.9/assets/js/rtafar.media.replacer.min.js
+++ /home/deploy/wp-safety.org/data/plugin-versions/real-time-auto-find-and-replace/1.8.0/assets/js/rtafar.media.replacer.min.js
@@ -1 +1 @@
-(...)"image"===e.type){var t="image"===e.type?e.url:e.thumb.src;return'<div class="image-item">\n                                        <img class="media-item-'.concat(e.id,'" data-id="').concat(e.id,'" src="').concat(t,'" alt="').concat(e.title,'" height="250px">\n                                        <button class="button-overlay btn-img-replace" data-id="').concat(e.id,'">Replace</button>\n                                    </div>')}
+(...)if("object"===r(t.data)&&0!==Object.keys(t.data).length){var a=jQuery(".image-container");a.empty(),t.data.forEach((function(t){var r=jQuery("<div/>",{class:"image-item"}),i=jQuery("<button/>",{class:"button-overlay btn-img-replace","data-id":t.id,text:"Replace"});if("image"===t.type){var o="image"===t.type?t.url:t.thumb.src;r.append(e.imagePreview(t.id,o,t.title,"250px"))}else"video"===t.type&&r.append(e.videoPreview(t.id,t.url,"250px"));r.append(i),a.append(r)}))}

Exploit Outline

To exploit this vulnerability, an attacker with Author-level access (or any role with `upload_files` capability) follows these steps: 1. Upload a legitimate image to the WordPress Media Library. 2. Update the image metadata (Title) via `wp-admin/post.php` to include an XSS payload designed to break out of an HTML attribute, such as: `poc" onerror="alert(document.domain)" x="`. 3. Wait for an administrator to access the plugin's 'Media Replacer' page at `wp-admin/admin.php?page=rtafar-media-replacer`. 4. When the administrator searches for a term matching the malicious image (e.g., 'poc'), the plugin performs an AJAX request (`query-attachments`) and uses jQuery's `.html()` method to render the results. 5. Because the `title` attribute from the AJAX response is concatenated directly into an `<img>` tag's `alt` attribute without escaping, the `onerror` event handler executes the attacker's script in the administrator's browser context.

Check if your site is affected.

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