CVE-2026-27421

Royal Addons for Elementor – Addons and Templates Kit for Elementor < 1.7.1053 - Authenticated (Contributor+) Stored Cross-Site Scripting

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

Description

The Royal Addons for Elementor – Addons and Templates Kit for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to 1.7.1053 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: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.7.1053
PublishedMay 7, 2026
Last updatedMay 11, 2026
Affected pluginroyal-elementor-addons

What Changed in the Fix

Changes introduced in v1.7.1053

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-27421**, a Stored Cross-Site Scripting (XSS) vulnerability in the Royal Addons for Elementor plugin. The vulnerability allows Contributor-level users to inject malicious scripts into pages via unsanitized Mega Menu settings. ### 1. Vulnerability Su…

Show full research plan

This exploitation research plan targets CVE-2026-27421, a Stored Cross-Site Scripting (XSS) vulnerability in the Royal Addons for Elementor plugin. The vulnerability allows Contributor-level users to inject malicious scripts into pages via unsanitized Mega Menu settings.

1. Vulnerability Summary

  • Vulnerability: Stored Cross-Site Scripting (XSS)
  • Location: admin/mega-menu.php (specifically the AJAX handler wpr_save_mega_menu_settings)
  • Root Cause: The plugin registers AJAX actions for saving Mega Menu configurations but fails to adequately sanitize input parameters before storing them in post meta. Furthermore, these values are later rendered in the admin dashboard or frontend menus without proper output escaping (e.g., using esc_attr() or esc_html()).
  • Privilege Level: Authenticated (Contributor+). While Mega Menus are typically an admin feature, the AJAX handlers and the underlying Custom Post Type (wpr_mega_menu) are often accessible or editable by lower-level users who have access to the Elementor editor.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpr_save_mega_menu_settings
  • Vulnerable Parameter(s): Settings associated with menu items, likely wpr_mm_custom_width or other text-based settings stored in the wpr-mega-menu-item meta.
  • Preconditions:
    1. The plugin "Royal Addons for Elementor" is active.
    2. At least one WordPress Menu and Menu Item must exist.
    3. The attacker has a Contributor-level account.

3. Code Flow

  1. Entry Point: An AJAX POST request is sent to admin-ajax.php with the action wpr_save_mega_menu_settings.
  2. Hook Registration: In admin/mega-menu.php, the action is registered:
    add_action( 'wp_ajax_wpr_save_mega_menu_settings', 'wpr_save_mega_menu_settings' );
  3. Vulnerable Handler: The (inferred) wpr_save_mega_menu_settings function retrieves the item_id and settings from $_POST.
  4. Insecure Storage: The handler uses update_post_meta($item_id, 'wpr_mm_settings', $settings) (or similar) without sanitizing the values within the $settings array.
  5. Sink: When the admin menu or the frontend page renders the menu, the function render_settings_popup (or its frontend equivalent) retrieves the meta and echoes it.
    • Example Sink in admin/mega-menu.php: The render_settings_popup function renders HTML for the settings. If any previously saved setting is echoed back into an attribute or text node without escaping, the XSS triggers.

4. Nonce Acquisition Strategy

The AJAX actions in admin/mega-menu.php require a nonce verified against the action string 'wpr-mega-menu-js'.

  1. Identify the Source: The nonce is enqueued via enqueue_scripts in admin/mega-menu.php.
  2. Localization Key: Based on plugin patterns, the nonce is likely localized in a global JavaScript object. We will search for WPRMegaMenu or WprConfig.
  3. Acquisition Steps:
    • Create a post/page and open the Elementor editor (accessible to Contributors).
    • Alternatively, navigate to any admin page where Royal Addons scripts are enqueued.
    • Execute the following in the browser console:
      browser_eval("window.WPRMegaMenu?.nonce || window.WprConfig?.nonce")
  4. Bypass Note: If wpr_save_mega_menu_settings lacks a current_user_can('manage_options') check (unlike wpr_create_mega_menu_template), a Contributor can exploit it using this nonce.

5. Exploitation Strategy

  1. Target Identification: Identify a valid menu_item_id. This can be done via wp_cli or by inspecting the Navigation Menus page.
  2. Craft Payload: Use a payload designed to break out of an HTML attribute:
    "><script>alert(document.domain)</script>
  3. HTTP Request (using http_request tool):
    • URL: https://<target>/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=wpr_save_mega_menu_settings&
      nonce=<NONCE>&
      item_id=<ITEM_ID>&
      settings[wpr_mm_custom_width]=600"><script>alert(1)</script>&
      settings[wpr_mm_enabled]=true
      
      (Note: Parameter structure settings[] is inferred based on standard Royal Addons AJAX patterns).

6. Test Data Setup

  1. Create Menu: wp menu create "Test Menu"
  2. Add Item: wp menu item add-post test-menu 1 (where 1 is a valid post ID).
  3. Identify ID: Get the Menu Item ID (this is a post of type nav_menu_item):
    wp post list --post_type=nav_menu_item --format=ids
  4. Create Attacker: wp user create attacker attacker@example.com --role=contributor --user_pass=password

7. Expected Results

  • The AJAX response should return a success status (e.g., {"success": true}).
  • The malicious payload should be stored in the database.
  • When an administrator visits the Appearance > Menus page, the render_settings_popup function will execute, rendering the injected script.

8. Verification Steps

  1. Database Check: Verify the payload is stored:
    wp post meta get <ITEM_ID> wpr_mm_settings
  2. Trigger Check: Use the browser_navigate tool to go to wp-admin/nav-menus.php as an administrator and check for the alert or the presence of the <script> tag in the DOM.

9. Alternative Approaches

  • Widget Injection: If the Mega Menu CPT wpr_mega_menu is directly editable by Contributors (as suggested by 'capability_type' => 'post'), the attacker can navigate to wp-admin/post.php?post=<MEGA_MENU_ID>&action=elementor and use an unescaped widget property (e.g., "Custom ID" field in a Royal Addons widget) to store the XSS.
  • Search Query XSS: Check if the wpr_backend_widget_search_query_results AJAX action reflects the search term unsanitized, which would lead to Reflected XSS.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Royal Addons for Elementor plugin is vulnerable to Stored Cross-Site Scripting via the Mega Menu settings. Authenticated attackers with Contributor-level access or higher can inject malicious scripts into menu item metadata because the plugin fails to sanitize input before storage and fails to escape it during output, leading to execution in the context of an administrator's browser.

Vulnerable Code

// admin/mega-menu.php:254
function wpr_save_mega_menu_settings() {

    $nonce = $_POST['nonce'];

    if ( !wp_verify_nonce( $nonce, 'wpr-mega-menu-js' )  || !current_user_can( 'manage_options' ) ) {
      exit; // Get out of here, the nonce is rotten!
    }

    if ( isset($_POST['item_settings']) ) {
        update_post_meta( $_POST['item_id'], 'wpr-mega-menu-settings', $_POST['item_settings'] );
    }

    wp_send_json_success($_POST['item_settings']);
}

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1052/admin/mega-menu.php	2026-03-20 08:14:26.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/royal-elementor-addons/1.7.1053/admin/mega-menu.php	2026-03-27 10:11:54.000000000 +0000
@@ -252,20 +260,81 @@
     <?php
 }
 
+/**
+ * Allowed mega menu setting keys (whitelist for sanitization).
+ *
+ * @return array
+ */
+function wpr_mega_menu_allowed_settings_keys() {
+    return [
+        'wpr_mm_enable',
+        'wpr_mm_position',
+        'wpr_mm_width',
+        'wpr_mm_custom_width',
+        'wpr_mm_mobile_content',
+        'wpr_mm_render',
+        'wpr_mm_icon_picker',
+        'wpr_mm_icon_color',
+        'wpr_mm_icon_size',
+        'wpr_mm_badge_text',
+        'wpr_mm_badge_color',
+        'wpr_mm_badge_bg_color',
+        'wpr_mm_badge_animation',
+    ];
+}
+
+/**
+ * Sanitize a single mega menu setting value.
+ *
+ * @param mixed $value Raw value.
+ * @param string $key Setting key.
+ * @return mixed Sanitized value.
+ */
+function wpr_sanitize_mega_menu_setting( $value, $key ) {
+    if ( ! is_scalar( $value ) ) {
+        return '';
+    }
+    $value = (string) $value;
+    if ( in_array( $key, [ 'wpr_mm_icon_color', 'wpr_mm_badge_color', 'wpr_mm_badge_bg_color' ], true ) ) {
+        return sanitize_text_field( $value );
+    }
+    if ( $key === 'wpr_mm_icon_size' || $key === 'wpr_mm_custom_width' ) {
+        return absint( $value );
+    }
+    return sanitize_text_field( $value );
+}
+
 // Save Mega Menu Settings
 function wpr_save_mega_menu_settings() {
 
-    $nonce = $_POST['nonce'];
+    if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wpr-mega-menu-js' ) || ! current_user_can( 'manage_options' ) ) {
+        wp_send_json_error( [ 'message' => 'Invalid request.' ] );
+        return;
+    }
 
-    if ( !wp_verify_nonce( $nonce, 'wpr-mega-menu-js' )  || !current_user_can( 'manage_options' ) ) {
-      exit; // Get out of here, the nonce is rotten!
+    if ( ! isset( $_POST['item_id'] ) || ! isset( $_POST['item_settings'] ) || ! is_array( $_POST['item_settings'] ) ) {
+        wp_send_json_error( [ 'message' => 'Missing item_id or item_settings.' ] );
+        return;
+    }
+
+    $item_id = absint( $_POST['item_id'] );
+    $item_post = get_post( $item_id );
+    if ( ! $item_post || $item_post->post_type !== 'nav_menu_item' ) {
+        wp_send_json_error( [ 'message' => 'Invalid menu item.' ] );
+        return;
     }
 
-    if ( isset($_POST['item_settings']) ) {
-        update_post_meta( $_POST['item_id'], 'wpr-mega-menu-settings', $_POST['item_settings'] );
+    $allowed_keys = wpr_mega_menu_allowed_settings_keys();
+    $raw         = wp_unslash( $_POST['item_settings'] );
+    $sanitized   = [];
+    foreach ( $allowed_keys as $key ) {
+        if ( array_key_exists( $key, $raw ) ) {
+            $sanitized[ $key ] = wpr_sanitize_mega_menu_setting( $raw[ $key ], $key );
+        }
     }
 
-    wp_send_json_success($_POST['item_settings']);
+    update_post_meta( $item_id, 'wpr-mega-menu-settings', $sanitized );
+    wp_send_json_success( $sanitized );
 }

Exploit Outline

1. Authenticate as a Contributor user and retrieve a valid security nonce for the 'wpr-mega-menu-js' action, typically accessible via localized JavaScript objects in the Elementor editor or dashboard. 2. Identify the target Navigation Menu Item ID (nav_menu_item post type). 3. Dispatch a POST request to /wp-admin/admin-ajax.php using the 'wpr_save_mega_menu_settings' action. 4. Provide a payload in the 'item_settings' array, specifically targeting text-based fields like 'wpr_mm_custom_width'. For example: settings[wpr_mm_custom_width]=600"><script>alert(document.domain)</script>. 5. The payload is saved directly into the post meta for the menu item. The script triggers when an administrator views the Appearance > Menus page and the settings popup for the affected menu item is rendered.

Check if your site is affected.

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