Better Find and Replace – AI-Powered Suggestions <= 1.7.9 - Authenticated (Author+) Stored Cross-Site Scripting via Uploaded Image Title
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:NTechnical Details
<=1.7.9What Changed in the Fix
Changes introduced in v1.8.0
Source Code
WordPress.org SVN# 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) andwp-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_filesandedit_postscapabilities 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
- Data Source: The attacker uploads an image. WordPress stores the title in the
wp_poststable (columnpost_title). - Data Retrieval: In the admin interface,
assets/js/rtafar.media.replacer.min.jsuses thequery-attachmentsAJAX action to find images. - 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), wheretis the unsanitized string.
- Inside
- Construction (Sink 2 - Preview Modal):
- In
setupReplaceButton, the plugin callswp.media.attachment(id).fetch(). - It uses the
imagePreviewfunction: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.
- In
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.
- Identify the Page: The media replacer tool is located at
wp-admin/admin.php?page=rtafar-media-replacer(inferred from JS naming). - Accessing the Page: The PoC agent should navigate to this page using
browser_navigate. - Extracting the Nonce: If the
query-attachmentscall requires a nonce, it is typically stored in thewp.mediaconfiguration.- JavaScript Check:
browser_eval("wp.media.view.settings.post.nonce"). - Alternative: Check for a localized object like
rtafar_media_replacer_vars.
- JavaScript Check:
5. Exploitation Strategy
- Upload Malicious Media:
- Authenticate as an Author.
- Upload a legitimate image file (e.g.,
image.jpg) usingwp-admin/async-upload.php. - Capture the
attachment_idfrom the response.
- Inject XSS Payload into Title:
- Use a
POSTrequest towp-admin/post.phpto update the attachment's metadata. - Payload:
poc" onerror="alert(document.domain)" x=" - This payload breaks out of the
alt="..."attribute.
- Use a
- 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 theonerrorevent.
6. Test Data Setup
- User: Create a user with the
authorrole. - 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
altattribute 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
onerrorhandler will fire, executingalert(document.domain).
8. Verification Steps
- Database Check:
wp db query "SELECT post_title FROM wp_posts WHERE post_type='attachment' AND post_title LIKE '%onerror%'".
- AJAX Response Check:
- Intercept the
query-attachmentsresponse in the browser or viahttp_requestand verify thetitlefield contains the raw payload:"title":"poc\" onerror=\"alert(document.domain)\" x=\"".
- Intercept the
- DOM Inspection:
- Use
browser_eval("document.querySelector('.image-container img')?.getAttribute('onerror')")to confirm the attribute was successfully injected into the DOM.
- Use
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 theimagePreviewlogic andsetupReplaceButtonlogic, 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
setupSearchInputfunction handles videos in a similar (though slightly different) unsanitized concatenation path.
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
@@ -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.