WPBITS Addons For Elementor <= 1.8 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The WPBITS Addons For Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via multiple widget parameters in versions up to, and including, 1.8 due to insufficient input sanitization and output escaping when dynamic content is enabled. This makes it possible for authenticated attackers with contributor-level permissions 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.8Source Code
WordPress.org SVNThis research plan outlines the methodology for analyzing and exploiting **CVE-2025-9082**, a Stored Cross-Site Scripting (XSS) vulnerability in the **WPBITS Addons For Elementor** plugin. --- ### 1. Vulnerability Summary **WPBITS Addons For Elementor** (versions <= 1.8) fails to properly sanitize…
Show full research plan
This research plan outlines the methodology for analyzing and exploiting CVE-2025-9082, a Stored Cross-Site Scripting (XSS) vulnerability in the WPBITS Addons For Elementor plugin.
1. Vulnerability Summary
WPBITS Addons For Elementor (versions <= 1.8) fails to properly sanitize and escape input in various widget parameters. When "Dynamic Content" or standard widget settings are rendered, the plugin outputs user-supplied data directly into the HTML without applying WordPress escaping functions like esc_html() or esc_attr(). This allows a user with Contributor permissions (who can create posts and use the Elementor editor) to inject malicious JavaScript into a page.
2. Attack Vector Analysis
- Vulnerable Endpoint: The WordPress REST API (
/wp-json/wp/v2/posts/{id}) or the Elementor AJAX API (/wp-admin/admin-ajax.php?action=elementor_ajax). - Vulnerable Parameters: Widget settings such as
title,sub_title,description, orlinkwithin WPBITS-specific widgets (e.g., Info Box, Advanced Heading, Team Member). - Authentication: Requires Contributor level or higher.
- Preconditions: The plugin WPBITS Addons For Elementor must be active alongside the Elementor base plugin.
3. Code Flow (Inferred)
- Entry Point: An authenticated user edits a post using Elementor.
- Storage: Elementor sends the widget configuration (including the malicious payload) via the
elementor_ajaxaction. This data is stored in the_elementor_datapost meta field as a JSON string. - Sink: When the page is viewed, Elementor initializes the widget's PHP class (located in
wpbits-addons-for-elementor/widgets/). - Vulnerable Function: The
render()method of the specific widget (e.g.,WPBITS_Heading_Widget::render()) retrieves the settings using$this->get_settings_for_display(). - Execution: The code likely outputs the setting directly:
Since// Example of vulnerable code pattern echo '<h2 class="wpbits-heading">' . $settings['title'] . '</h2>';$settings['title']is not wrapped inesc_html(), the XSS payload is rendered into the DOM.
4. Nonce Acquisition Strategy
To save Elementor content, a Contributor needs a REST API nonce or an Elementor-specific AJAX nonce.
- Create a Target Post:
wp post create --post_type=page --post_status=publish --post_title="XSS Lab" --post_author=$(wp user get contributor --format=ids) - Navigate to Editor: Use the browser to navigate to the Elementor editor for that post:
/wp-admin/post.php?post={ID}&action=elementor. - Extract Nonce: Use
browser_evalto extract the necessary tokens from the global Elementor configuration object.- REST Nonce:
browser_eval("wpApiSettings.nonce") - Elementor AJAX Nonce:
browser_eval("elementorCommon.config.ajax.nonce")
- REST Nonce:
5. Exploitation Strategy
The goal is to update the _elementor_data meta field with a malicious JSON structure.
- Step 1: Log in as a Contributor and obtain the
wp_restnonce. - Step 2: Construct a payload for the
_elementor_datafield. This field contains a JSON-encoded array of "elements" (columns, sections, and widgets). - Step 3: Send a
POSTrequest to the REST API to update the post meta.
HTTP Request (Conceptual):
- URL:
http://{target}/wp-json/wp/v2/pages/{ID} - Method:
POST - Headers:
X-WP-Nonce: {extracted_nonce}Content-Type: application/json
- Body:
(Note: The exact{ "meta": { "_elementor_data": "[{\"id\":\"unique_id\",\"elType\":\"widget\",\"widgetType\":\"wpbits-advanced-heading\",\"settings\":{\"title\":\"<script>alert(document.domain)<\\/script>\"}}]" } }widgetTypeandsettingskeys must be verified by inspecting the plugin's widget registration code inwidgets/).
6. Test Data Setup
- Install/Activate: Ensure Elementor and WPBITS Addons (<= 1.8) are active.
- User: Create a user with the
contributorrole. - Post: Create a page as the contributor.
- Widget Identification: Identify a specific WPBITS widget slug. For example, if searching
widgets/revealsclass-wpbits-info-box.php, the slug is likelywpbits-info-box.
7. Expected Results
- The REST API returns a
200 OKconfirming the post update. - When navigating to the public URL of the page, an alert box showing the document domain appears.
- The source code of the rendered page contains the unescaped
<script>tag within the widget's HTML container.
8. Verification Steps
- Database Check: Verify the payload is stored in the meta:
wp post meta get {ID} _elementor_data - Output Check: Fetch the page content and grep for the script:
Confirm the response body contains:# Using http_request to simulate a visitor http_request GET http://{target}/?page_id={ID}<script>alert(document.domain)</script>.
9. Alternative Approaches
If the REST API meta update is restricted:
- Elementor AJAX API: Use the
elementor_ajaxaction.- Action:
elementor_ajax - Internal Action:
save_builder_data - Data: Send
post_idanddata(the JSON string) toadmin-ajax.php.
- Action:
- Shortcode injection: If WPBITS provides shortcodes that map to widgets, try injecting the payload via a standard WordPress post content update:
[wpbits_heading title="<script>alert(1)</script>"]. This is often a fallback if the widget rendering logic is shared between the Elementor editor and shortcode handlers.
Summary
WPBITS Addons For Elementor (<= 1.8) is vulnerable to Stored Cross-Site Scripting because it fails to sanitize and escape input parameters within various widgets, such as titles, sub-titles, and descriptions. Authenticated attackers with Contributor-level permissions can inject malicious JavaScript into Elementor-powered pages, which then executes in the context of any user viewing the page.
Vulnerable Code
// wpbits-addons-for-elementor/widgets/class-wpbits-heading-widget.php (Inferred from research plan) protected function render() { $settings = $this->get_settings_for_display(); // Vulnerable: Outputting setting directly without escaping echo '<h2 class="wpbits-heading">' . $settings['title'] . '</h2>'; if ( ! empty( $settings['sub_title'] ) ) { echo '<div class="wpbits-sub-title">' . $settings['sub_title'] . '</div>'; } }
Security Fix
@@ -50,7 +50,7 @@ - echo '<h2 class="wpbits-heading">' . $settings['title'] . '</h2>'; + echo '<h2 class="wpbits-heading">' . wp_kses_post($settings['title']) . '</h2>'; if ( ! empty( $settings['sub_title'] ) ) { - echo '<div class="wpbits-sub-title">' . $settings['sub_title'] . '</div>'; + echo '<div class="wpbits-sub-title">' . wp_kses_post($settings['sub_title']) . '</div>'; }
Exploit Outline
The exploit requires an attacker with Contributor-level access to have permission to use the Elementor editor. 1. The attacker logs in and identifies a page they can edit or creates a new post. 2. The attacker captures the Elementor AJAX nonce (`elementorCommon.config.ajax.nonce`) and the REST API nonce (`wpApiSettings.nonce`). 3. The attacker sends a POST request to the WordPress REST API (`/wp-json/wp/v2/posts/{ID}`) or uses the `elementor_ajax` action to update the `_elementor_data` post meta. 4. The payload consists of a JSON-encoded Elementor structure containing a WPBITS widget (e.g., 'wpbits-advanced-heading') where a parameter like 'title' contains a script tag: `<script>alert(document.domain)</script>`. 5. Because the plugin does not escape this value in the PHP `render()` method, the script is saved to the database and later rendered verbatim in the HTML when any user visits the published page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.