CVE-2025-9082

WPBITS Addons For Elementor <= 1.8 - 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.8.1
Patched in
1d
Time to patch

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: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.8
PublishedJanuary 27, 2026
Last updatedJanuary 28, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

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…

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, or link within 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)

  1. Entry Point: An authenticated user edits a post using Elementor.
  2. Storage: Elementor sends the widget configuration (including the malicious payload) via the elementor_ajax action. This data is stored in the _elementor_data post meta field as a JSON string.
  3. Sink: When the page is viewed, Elementor initializes the widget's PHP class (located in wpbits-addons-for-elementor/widgets/).
  4. Vulnerable Function: The render() method of the specific widget (e.g., WPBITS_Heading_Widget::render()) retrieves the settings using $this->get_settings_for_display().
  5. Execution: The code likely outputs the setting directly:
    // Example of vulnerable code pattern
    echo '<h2 class="wpbits-heading">' . $settings['title'] . '</h2>'; 
    
    Since $settings['title'] is not wrapped in esc_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.

  1. 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)
    
  2. Navigate to Editor: Use the browser to navigate to the Elementor editor for that post: /wp-admin/post.php?post={ID}&action=elementor.
  3. Extract Nonce: Use browser_eval to 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")

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_rest nonce.
  • Step 2: Construct a payload for the _elementor_data field. This field contains a JSON-encoded array of "elements" (columns, sections, and widgets).
  • Step 3: Send a POST request 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:
    {
      "meta": {
        "_elementor_data": "[{\"id\":\"unique_id\",\"elType\":\"widget\",\"widgetType\":\"wpbits-advanced-heading\",\"settings\":{\"title\":\"<script>alert(document.domain)<\\/script>\"}}]"
      }
    }
    
    (Note: The exact widgetType and settings keys must be verified by inspecting the plugin's widget registration code in widgets/).

6. Test Data Setup

  1. Install/Activate: Ensure Elementor and WPBITS Addons (<= 1.8) are active.
  2. User: Create a user with the contributor role.
  3. Post: Create a page as the contributor.
  4. Widget Identification: Identify a specific WPBITS widget slug. For example, if searching widgets/ reveals class-wpbits-info-box.php, the slug is likely wpbits-info-box.

7. Expected Results

  • The REST API returns a 200 OK confirming 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

  1. Database Check: Verify the payload is stored in the meta:
    wp post meta get {ID} _elementor_data
    
  2. Output Check: Fetch the page content and grep for the script:
    # Using http_request to simulate a visitor
    http_request GET http://{target}/?page_id={ID}
    
    Confirm the response body contains: <script>alert(document.domain)</script>.

9. Alternative Approaches

If the REST API meta update is restricted:

  • Elementor AJAX API: Use the elementor_ajax action.
    • Action: elementor_ajax
    • Internal Action: save_builder_data
    • Data: Send post_id and data (the JSON string) to admin-ajax.php.
  • 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.
Research Findings
Static analysis — not yet PoC-verified

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

--- wpbits-addons-for-elementor/widgets/class-wpbits-heading-widget.php
+++ wpbits-addons-for-elementor/widgets/class-wpbits-heading-widget.php
@@ -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.