Animation Addons for Elementor – GSAP Motion Elementor Addons & Website Templates <= 2.6.3 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The Animation Addons for Elementor – GSAP Motion Elementor Addons & Website Templates plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.6.3 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.3What Changed in the Fix
Changes introduced in v2.6.4
Source Code
WordPress.org SVNThis research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the **Animation Addons for Elementor** plugin (<= 2.6.3). The vulnerability arises from the plugin's failure to escape post-related data (specifically titles) when rendered through its Elementor widgets. --- ### 1. Vul…
Show full research plan
This research plan targets a Stored Cross-Site Scripting (XSS) vulnerability in the Animation Addons for Elementor plugin (<= 2.6.3). The vulnerability arises from the plugin's failure to escape post-related data (specifically titles) when rendered through its Elementor widgets.
1. Vulnerability Summary
- Vulnerability: Authenticated (Contributor+) Stored Cross-Site Scripting
- Location:
inc/AAE_Post_Handler_Trait.phpwithin therender_title()andwcf_wrap_first_n_words()functions. - Cause: The plugin retrieves post titles using
get_the_title()and processes them throughwcf_wrap_first_n_words(). This function wraps parts of the title in a<span>tag but returns the raw, unescaped string, which is then echoed directly to the page. - Impact: A user with Contributor-level permissions can create a post with a malicious title containing JavaScript. When an administrator or any visitor views a page where an Animation Addons widget (like "Post Grid" or "Loop Grid") displays that post, the script executes in their browser context.
2. Attack Vector Analysis
- Entry Point: WordPress Post Title.
- Vulnerable Parameter:
post_title. - Authentication Level: Contributor or higher (anyone capable of creating/editing posts).
- Preconditions: The site must have at least one page using an Animation Addons widget that renders post titles (e.g.,
Loop Grid,Post Grid).
3. Code Flow
- Source: A Contributor creates/edits a post. The malicious payload is saved in the
wp_poststable underpost_title. - Sink Path:
- An Elementor widget using the
AAE_Post_Handler_Trait(e.g., a Post Grid widget) is rendered on the frontend. - The widget calls
$this->render_title()(defined ininc/AAE_Post_Handler_Trait.php). - Inside
render_title(), the code fetches the title:$title = $this->trim_words( get_the_title(), $max_length );. - It then calls
echo $this->wcf_wrap_first_n_words( $title, $highlight_title_length );. - The Sink:
wcf_wrap_first_n_words()(also ininc/AAE_Post_Handler_Trait.php) manipulates the string and returns it:return '<span class="' . $class . '">' . $text . '</span>'; - Because
$text(the title) is never passed throughesc_html(), the payload is executed.
- An Elementor widget using the
4. Nonce Acquisition Strategy
This exploit leverages the standard WordPress post creation/editing mechanism. No plugin-specific AJAX nonces are required for the primary payload injection via the post title.
However, if the environment requires extracting a nonce to verify widget rendering or to interact with the Elementor Editor (if the Contributor is editing a page directly), the following applies:
- Variable Name:
WCF_Addons_Editor(found inclass-plugin.php). - Nonce Key:
_wpnonce. - Extraction Method:
- Create a page:
wp post create --post_type=page --post_status=publish --post_content='[any_shortcode]' - Navigate to the editor for that page.
- Run:
browser_eval("window.WCF_Addons_Editor?._wpnonce").
- Create a page:
5. Exploitation Strategy
- Log in as Contributor.
- Create a Malicious Post: Use the
http_requesttool to create a post with a script payload in the title. - Ensure Rendering: Use the
wp-clito ensure at least one page exists that contains an Animation Addons widget. - Trigger Execution: Access the page as an Admin user.
Payload Request
POST /wp-admin/post.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=editpost&post_ID=[POST_ID]&post_title=Test <img src=x onerror=alert(document.domain)>&post_status=publish
6. Test Data Setup
- User: A user with the
contributorrole. - Vulnerable Post: A post created by the contributor with the payload:
"><script>alert('XSS')</script>. - Display Page: A published page containing an Animation Addons widget that renders recent posts.
- Note: If no such page exists, create one via WP-CLI using a known widget slug from
config.php(e.g., a Post Grid or Loop Grid).
- Note: If no such page exists, create one via WP-CLI using a known widget slug from
7. Expected Results
- When viewing the page containing the widget, the browser should render:
<span class="highlight">Test <img src=x onerror=alert(document.domain)></span> - The
onerrorevent will trigger, and an alert box displaying the document domain will appear.
8. Verification Steps
- Check Database:
wp db query "SELECT post_title FROM wp_posts WHERE post_title LIKE '%<img%'". - Inspect Response: Use
http_request(GET) to the page and verify that the raw payload string exists inside a<span>tag with the classwcf-post-title.
9. Alternative Approaches
- Read More XSS: The
render_read_more()function useswp_kses_post( $read_more ). Whilewp_kses_postis generally safe, it allows some HTML. Check if attributes or specific tags can be abused if the Contributor can modify the widget settings directly within Elementor. - Widget Settings: If a Contributor has access to the Elementor editor, they can inject payloads into widget-specific controls (e.g., "Image Box Title"). If these are rendered via a similar path without
esc_html, they are equally vulnerable. Look for therender()method in the widget classes (e.g.,WCF_ADDONS\Widgets\Image_Box).
Summary
The Animation Addons for Elementor plugin is vulnerable to Stored Cross-Site Scripting via post titles rendered in various Elementor widgets. Authenticated attackers with Contributor-level access or higher can inject malicious scripts into post titles, which are then rendered and executed without escaping when viewed by other users, including administrators.
Vulnerable Code
// inc/AAE_Post_Handler_Trait.php function wcf_wrap_first_n_words( $text, $n, $class = 'highlight' ) { // Split the text into an array of words $words = explode( ' ', $text ); // Check if the text has enough words to wrap if ( count( $words ) >= $n ) { // Extract the first N words and wrap them in a span tag $wrapped_words = array_slice( $words, 0, $n ); $remaining_words = array_slice( $words, $n ); // Create the wrapped portion $wrapped = '<span class="' . $class . '">' . implode( ' ', $wrapped_words ) . '</span>'; // Combine the wrapped portion with the remaining words return $wrapped . ' ' . implode( ' ', $remaining_words ); } // If there are fewer words than N, wrap the whole text return '<span class="' . $class . '">' . $text . '</span>'; } --- // inc/AAE_Post_Handler_Trait.php line 51 $title = $this->trim_words( get_the_title(), $max_length ); $highlight_title_length = (int) $this->get_settings( 'highlight_title_length' ); echo $this->wcf_wrap_first_n_words( $title, $highlight_title_length ); // Wrap first 2 words
Security Fix
@@ -24,11 +24,11 @@ $wrapped_words = array_slice( $words, 0, $n ); $remaining_words = array_slice( $words, $n ); // Create the wrapped portion - $wrapped = '<span class="' . $class . '">' . implode( ' ', $wrapped_words ) . '</span>'; + $wrapped = '<span class="' . esc_attr( $class ) . '">' . esc_html( implode( ' ', $wrapped_words ) ) . '</span>'; // Combine the wrapped portion with the remaining words - return $wrapped . ' ' . implode( ' ', $remaining_words ); + return $wrapped . ' ' . esc_html( implode( ' ', $remaining_words ) ); } // If there are fewer words than N, wrap the whole text - return '<span class="' . $class . '">' . $text . '</span>'; + return '<span class="' . esc_attr( $class ) . '">' . esc_html( $text ) . '</span>'; }
Exploit Outline
The exploit is achieved by an authenticated user (Contributor or higher) following these steps: 1. Log in to the WordPress admin dashboard and create a new post. 2. Set the post title to a malicious JavaScript payload (e.g., `"><img src=x onerror=alert(1)>`). 3. Publish or save the post. 4. Ensure there is a page on the site utilizing an Animation Addons widget that renders recent posts (such as a Post Grid, Loop Grid, or Archive widget). 5. When an administrator or any other user views the page containing the widget, the plugin calls `wcf_wrap_first_n_words()` via `AAE_Post_Handler_Trait`, which returns the unescaped payload wrapped in a `<span>` tag. The payload is then echoed directly into the page content, triggering the execution of the script in the victim's browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.