WPZOOM Addons for Elementor – Starter Templates & Widgets <= 1.3.4 - Reflected Cross-Site Scripting
Description
The WPZOOM Addons for Elementor – Starter Templates & Widgets plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to, and including, 1.3.4 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:NTechnical Details
<=1.3.4What Changed in the Fix
Changes introduced in v1.3.5
Source Code
WordPress.org SVN# Research Plan: CVE-2026-39597 - WPZOOM Addons for Elementor Reflected XSS ## 1. Vulnerability Summary The **WPZOOM Addons for Elementor – Starter Templates & Widgets** plugin (versions <= 1.3.4) is vulnerable to Reflected Cross-Site Scripting (XSS) via the `wpz_posts_grid_load_more` AJAX action. …
Show full research plan
Research Plan: CVE-2026-39597 - WPZOOM Addons for Elementor Reflected XSS
1. Vulnerability Summary
The WPZOOM Addons for Elementor – Starter Templates & Widgets plugin (versions <= 1.3.4) is vulnerable to Reflected Cross-Site Scripting (XSS) via the wpz_posts_grid_load_more AJAX action. The vulnerability exists in the WPZOOM_Elementor_Ajax_Post_Grid class where the title_tag parameter, provided within a JSON-encoded posts_data POST parameter, is reflected into the page without sufficient sanitization or attribute escaping. Specifically, the value is used as an HTML tag name, allowing an attacker to inject attributes like onerror.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
wpz_posts_grid_load_more - Vulnerable Parameter:
posts_data[title_tag] - Authentication: Unauthenticated (available via
wp_ajax_nopriv_wpz_posts_grid_load_more). - Preconditions:
- A valid WordPress nonce for the action
wpz_posts_grid_load_more. - At least one published post must exist so that the
WP_Queryreturns results, triggering the rendering logic.
- A valid WordPress nonce for the action
- Payload Type: Reflected (via POST request).
3. Code Flow
- Entry Point: The
ajax_post_grid_load_more()function inincludes/wpzoom-elementor-ajax-posts-grid.phpis triggered by the AJAX action. - Input Handling:
- The
nonceis verified (Line 72). $_POST['posts_data']is passed throughsanitize_text_field()and thenjson_decode()(Lines 74-75).- The resulting array is stored in
self::$settings(Line 77).
- The
- Execution Path:
- A
WP_Queryis executed based on other parameters in$data
- A
Summary
The plugin is vulnerable to unauthenticated Reflected Cross-Site Scripting (XSS) via the 'wpz_posts_grid_load_more' AJAX action. This occurs because the 'title_tag' parameter within the JSON-encoded 'posts_data' POST parameter is reflected into the page as an HTML tag name without validation or escaping.
Vulnerable Code
// includes/wpzoom-elementor-ajax-posts-grid.php line 204 protected function render_title() { $settings = $this->get_settings(); $show_title = $settings[ 'show_title' ]; if ( 'yes' !== $show_title ) { return; } $title_tag = $settings[ 'title_tag' ]; ?> <<?php echo $title_tag; // WPCS: XSS OK. ?> class="title"> <a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( get_the_title() ); ?>"><?php the_title(); ?></a> </<?php echo $title_tag; // WPCS: XSS OK. ?>> <?php }
Security Fix
@@ -1743,7 +1743,7 @@ <?php if ( $popup_video_type === 'self_hosted' && $is_video_popup_self_hosted ): ?> <div id="zoom-popup-<?php echo the_ID(); ?>" class="animated slow mfp-hide" - data-src="<?php echo $popup_self_hosted_src; ?>"> + data-src="<?php echo esc_url( $popup_self_hosted_src ); ?>"> <div class="mfp-iframe-scaler"> <?php echo wp_video_shortcode( @@ -1771,12 +1771,12 @@ <a class="reel_video_item" href="<?php echo esc_url( get_permalink() ); ?>" title="<?php echo esc_attr( get_the_title() ); ?>"> <?php if($has_video_popup): ?> <div class="entry-thumbnail-popover<?php if ($lightbox_open_thumb) { ?> lightbox_open_full<?php } ?>"> - <div class="entry-thumbnail-popover-content popover-content--animated" data-show-caption="<?php echo $show_popup_caption ?>"> + <div class="entry-thumbnail-popover-content popover-content--animated" data-show-caption="<?php echo esc_attr( $show_popup_caption ); ?>"> <?php if ( $popup_video_type === 'self_hosted' && $is_video_popup_self_hosted ): ?> <span href="#zoom-popup-<?php echo the_ID(); ?>" class="mfp-inline portfolio-popup-video"></span> <?php elseif ( $popup_video_type === 'external_hosted' && ! empty( $portfolio_video_popup_url ) ): ?> <span class="mfp-iframe portfolio-popup-video" - href="<?php echo $portfolio_video_popup_url; ?>"></span> + href="<?php echo esc_url( $portfolio_video_popup_url ); ?>"></span> <?php endif; ?> </div> </div> @@ -1833,11 +1833,11 @@ <ul> <?php if ($enable_director_name && $video_director) { ?> - <li><?php echo $video_director; ?></li> + <li><?php echo esc_html( $video_director ); ?></li> <?php } ?> <?php if ($enable_year && $video_year) { ?> - <li><?php echo $video_year; ?></li> + <li><?php echo esc_html( $video_year ); ?></li> <?php } ?> <?php if ( $enable_category ) : ?><li> @@ -1869,11 +1869,11 @@ <ul> <?php if ($enable_director_name && $video_director) { ?> - <li><?php echo $video_director; ?></li> + <li><?php echo esc_html( $video_director ); ?></li> <?php } ?> <?php if ($enable_year && $video_year) { ?> - <li><?php echo $video_year; ?></li> + <li><?php echo esc_html( $video_year ); ?></li> <?php } ?> <?php if ( $enable_category ) : ?><li> @@ -1907,11 +1907,11 @@ <ul> <?php if ($enable_director_name && $video_director) { ?> - <li><?php echo $video_director; ?></li> + <li><?php echo esc_html( $video_director ); ?></li> <?php } ?> <?php if ($enable_year && $video_year) { ?> - <li><?php echo $video_year; ?></li> + <li><?php echo esc_html( $video_year ); ?></li> <?php } ?> <?php if ( $enable_category ) : ?><li> @@ -1131,7 +1131,7 @@ <a href="#zoom-popup-<?php echo get_the_ID(); ?>" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' data-popup-type="inline" class="popup-video" aria-label="Watch Video"></a> <?php elseif(!empty($video_background_popup_url)): ?> - <a data-popup-type="iframe" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' class="popup-video animated slow pulse" href="<?php echo $video_background_popup_url ?>" aria-label="Watch Video"></a> + <a data-popup-type="iframe" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' class="popup-video animated slow pulse" href="<?php echo esc_url( $video_background_popup_url ); ?>" aria-label="Watch Video"></a> <?php endif; ?> <?php } /* End Inspiro PRO markup */ ?> @@ -1174,7 +1174,7 @@ if( ( 'inspiro' === $current_theme && class_exists( 'WPZOOM' ) && $align_vertical != 'bottom' ) || ( 'wpzoom-inspiro-pro' === $current_theme && $align == 'center' ) ) { ?> <?php if($popup_video_type === 'self_hosted' && $is_video_popup): ?> - <div id="zoom-popup-<?php echo get_the_ID(); ?>" class="animated slow mfp-hide" data-src ="<?php echo $popup_final_external_src ?>"> + <div id="zoom-popup-<?php echo get_the_ID(); ?>" class="animated slow mfp-hide" data-src ="<?php echo esc_url( $popup_final_external_src ); ?>"> <div class="mfp-iframe-scaler"> @@ -1192,7 +1192,7 @@ <a href="#zoom-popup-<?php echo get_the_ID(); ?>" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' data-popup-type="inline" class="popup-video" aria-label="Watch Video"></a> <?php elseif(!empty($video_background_popup_url)): ?> - <a data-popup-type="iframe" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' class="popup-video animated slow pulse" href="<?php echo $video_background_popup_url ?>" aria-label="Watch Video"></a> + <a data-popup-type="iframe" data-popup-video-options='<?php echo json_encode( $encode_lightbox_video_opts ); ?>' class="popup-video animated slow pulse" href="<?php echo esc_url( $video_background_popup_url ); ?>" aria-label="Watch Video"></a> <?php endif; ?> <?php } /* End Inspiro Premium markup */ ?> @@ -210,8 +210,8 @@ return; } - $title_tag = $settings[ 'title_tag' ]; - + $title_tag = \Elementor\Utils::validate_html_tag( $settings[ 'title_tag' ] ); + ?> <<?php echo $title_tag; // WPCS: XSS OK. ?> class="title"> <a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( get_the_title() ); ?>"><?php the_title(); ?></a>
Exploit Outline
The exploit targets the 'wpz_posts_grid_load_more' AJAX action via the /wp-admin/admin-ajax.php endpoint. An attacker sends a POST request with the 'action' set to 'wpz_posts_grid_load_more', a valid 'nonce', and a 'posts_data' parameter containing a JSON-encoded object. By setting the 'title_tag' key within this JSON object to a malicious payload (e.g., 'img src=x onerror=alert(1)'), the plugin renders the string directly as an HTML tag name when iterating through posts. Because the action is hooked to 'wp_ajax_nopriv_', it is accessible to unauthenticated attackers, provided they can obtain a valid nonce through social engineering or other means.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.