SEO Friendly Images <= 3.0.5 - Authenticated (Contributor+) Stored Cross-Site Scripting
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:NTechnical Details
<=3.0.5# 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.phpor/wp-admin/post.php) - Vulnerable Parameter: Post Title or Custom Meta fields (e.g.,
_seo_image_altor 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_contentto automatically addaltandtitletags based on defined patterns (e.g.,%titleor%name).
3. Code Flow (Inferred)
- Entry Point: A Contributor creates or edits a post.
- Input: The user sets a Post Title or specific image metadata containing a payload like
"><script>alert(1)</script>. - Processing: The plugin registers a filter on
the_content(e.g.,add_filter('the_content', 'seo_friendly_images_process');). - Transformation: Inside the processing function (likely using
preg_replace_callbackor a DOM parser), the plugin identifies<img>tags. - 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. - Vulnerable Sink: The plugin constructs the new
altortitlestring and concatenates it into the<img>tag:
If// Example of vulnerable logic $new_tag = str_replace('alt="', 'alt="' . $computed_alt_value . '" ', $old_tag);$computed_alt_value(derived from the user-controlled Post Title) is not passed throughesc_attr(), the attribute is broken, and the script is injected. - Output: The modified content is returned to
the_contentand 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
_wpnonceprovided in the post editor form. - Manual Extraction (if needed for AJAX meta updates):
- Navigate to the Post Editor as a Contributor.
- Use
browser_evalto extract the post nonce if the plugin uses a custom AJAX save:browser_eval("document.querySelector('#_wpnonce').value")
- Standard Post Save: The
http_requesttool will simulate a standardPOSTtowp-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
- 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 attributeshould contain%title.
- Check settings page:
- User: Create a user with the
contributorrole. - Post: A post ID must be obtained (via
wp post createvia CLI) before sending thePOSTrequest topost.php.
7. Expected Results
When viewing the post as an Administrator:
- The HTML source for the image will look something like:
<img src="..." alt="Test Image Post"><script>alert(document.domain)</script>"> - An alert box showing the document domain will appear.
8. Verification Steps
- WP-CLI Check: Verify the post title was saved correctly.
wp post get [ID] --field=post_title - 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:
- 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). - 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.
- 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]
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
@@ -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.