WF-e5b98a2c-faed-4e2b-8b94-31e2be95ad40-wpzoom-elementor-addons

WPZOOM Addons for Elementor – Starter Templates & Widgets <= 1.3.4 - Unauthenticated Reflected Cross-Site Scripting via 'title_tag' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.1
CVSS Score
6.1
CVSS Score
medium
Severity
1.3.5
Patched in
1d
Time to patch

Description

The WPZOOM Addons for Elementor – Starter Templates & Widgets plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the 'title_tag' parameter 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 will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.3.4
PublishedFebruary 26, 2026
Last updatedFebruary 26, 2026

What Changed in the Fix

Changes introduced in v1.3.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: Unauthenticated Reflected XSS in WPZOOM Addons for Elementor ## 1. Vulnerability Summary The **WPZOOM Addons for Elementor – Starter Templates & Widgets** plugin (versions <= 1.3.4) is vulnerable to Unauthenticated Reflected Cross-Site Scripting. The vulnerability exists in the `WP…

Show full research plan

Research Plan: Unauthenticated Reflected XSS in WPZOOM Addons for Elementor

1. Vulnerability Summary

The WPZOOM Addons for Elementor – Starter Templates & Widgets plugin (versions <= 1.3.4) is vulnerable to Unauthenticated Reflected Cross-Site Scripting. The vulnerability exists in the WPZOOM_Elementor_Ajax_Post_Grid class, specifically within the render_title() method. The title_tag parameter, which is passed via an AJAX request, is used to dynamically generate HTML tags without sufficient sanitization or output escaping. An attacker can inject arbitrary HTML/JavaScript into this parameter, which is then reflected back to the user when the AJAX response is rendered.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: wpz_posts_grid_load_more
  • Hook: Registered via wp_ajax_nopriv_wpz_posts_grid_load_more (accessible to unauthenticated users).
  • Vulnerable Parameter: title_tag (nested inside the JSON-encoded posts_data POST parameter).
  • Authentication: None required.
  • Preconditions:
    1. A valid WordPress nonce for the action wpz_posts_grid_load_more.
    2. At least one published post must exist on the site (to satisfy the WP_Query condition and trigger the inclusion of the layout file).

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=wpz_posts_grid_load_more.
  2. Nonce Verification: includes/wpzoom-elementor-ajax-posts-grid.php (Line 81) calls wp_verify_nonce( $_POST['nonce'], 'wpz_posts_grid_load_more' ).
  3. Data Processing:
    • $_POST['posts_data'] is sanitized with sanitize_text_field() and then json_decode()ed into an array $data (Lines 85-86).
    • self::$settings is populated with this array (Line 88).
  4. Query Execution: WP_Query is executed using parameters from $data (Lines 90-116).
  5. Layout Inclusion: If the query returns posts, the plugin includes a layout file, e.g., includes/widgets/posts-grid/layouts/layout-1.php (Line 120).
  6. Sink: The layout file (not provided, but inferred by class logic) calls $this->render_title().
  7. Execution: render_title() (Lines 216-234) retrieves $title_tag from $settings. It then echos this value directly into the HTML output:
    <<?php echo $title_tag; // WPCS: XSS OK. ?> class="title">
    
    The comment // WPCS: XSS OK. indicates that the developer explicitly bypassed security linting for this line.

4. Nonce Acquisition Strategy

The nonce wpz_posts_grid_load_more is required for the exploit. It is typically localized for the frontend when a "Posts Grid" widget is present on a page.

  1. Setup: Create a page containing a "Posts Grid" widget. Since this is an Elementor plugin, we need to ensure the widget's assets are loaded.
  2. Identification: The plugin likely localizes the nonce in a variable. Based on common naming conventions in this plugin (like wpzoom_admin_data in the main file), look for a variable named something like wpz_posts_grid_settings.
  3. Execution:
    • Use wp post create to create a test page.
    • Navigate to the page using browser_navigate.
    • Extract the nonce using browser_eval:
      // (Inferred variable name, to be verified by the agent)
      window.wpz_posts_grid_settings?.nonce 
      // OR search for any localized object containing the action string
      

5. Exploitation Strategy

  1. Target: /wp-admin/admin-ajax.php
  2. Method: POST
  3. Headers: Content-Type: application/x-www-form-urlencoded
  4. Parameters:
    • action: wpz_posts_grid_load_more
    • nonce: [EXTRACTED_NONCE]
    • offset: 0
    • posts_data: A JSON string containing the payload:
      {
        "posts_per_page": 1,
        "show_title": "yes",
        "title_tag": "img src=x onerror=alert(document.domain)",
        "grid_style": 1
      }
      
  5. Payload Formulation: The title_tag value img src=x onerror=alert(document.domain) will result in the following HTML in the response:
    <img src=x onerror=alert(document.domain) class="title">
    

6. Test Data Setup

  1. Create a Post: Ensure the grid has data to render.
    wp post create --post_type=post --post_title="PoC Post" --post_status=publish
    
  2. Create a Page with Widget: To trigger nonce localization. If the exact Elementor block is unknown, the agent should search the plugin for add_shortcode or widget registration names.
    # Search for widget name
    grep -r "get_name" includes/widgets/
    
  3. Publish the Page:
    wp post create --post_type=page --post_title="XSS Test Page" --post_status=publish --post_content="[wpzoom_posts_grid]" # (Guessing shortcode if exists)
    

7. Expected Results

  • The AJAX response (HTML) should contain the injected payload: <img src=x onerror=alert(document.domain) class="title">.
  • In a browser context, the alert box would execute.

8. Verification Steps

  1. Response Check: Use the http_request tool and verify that the response body contains the string onerror=alert(document.domain).
  2. Path Confirmation: Verify the code path by checking if the plugin's render_title function is indeed called during the AJAX request by observing the response structure.

9. Alternative Approaches

  • Tag Breakout: If the title_tag is used in multiple places (like the closing tag), try script>alert(1)</script.
  • JSON Encoding: If sanitize_text_field interferes with the payload, remember that posts_data is JSON-encoded, so double-escaping or Unicode escapes (\u003c) might be necessary depending on how stripslashes and json_decode handle the input.
  • Different Layouts: The grid_style parameter determines which layout file is included. If layout-1.php is missing or doesn't call render_title, try other integers (1, 2, 3, etc.).
Research Findings
Static analysis — not yet PoC-verified

Summary

The WPZOOM Addons for Elementor plugin is vulnerable to unauthenticated reflected Cross-Site Scripting (XSS) via the 'title_tag' parameter in the 'wpz_posts_grid_load_more' AJAX action. This occurs because the plugin fails to sanitize or escape input used as an HTML tag name, allowing attackers to inject arbitrary scripts that execute when a user views the rendered AJAX response.

Vulnerable Code

// includes/wpzoom-elementor-ajax-posts-grid.php line 224
	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

--- /home/deploy/wp-safety.org/data/plugin-versions/wpzoom-elementor-addons/1.3.4/includes/wpzoom-elementor-ajax-posts-grid.php	2026-02-10 20:54:40.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wpzoom-elementor-addons/1.3.5/includes/wpzoom-elementor-ajax-posts-grid.php	2026-02-23 15:59:50.000000000 +0000
@@ -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 requires an attacker to obtain a valid nonce for the 'wpz_posts_grid_load_more' action, which is typically localized in the frontend when a 'Posts Grid' widget is present. An unauthenticated attacker sends a POST request to '/wp-admin/admin-ajax.php' with 'action=wpz_posts_grid_load_more' and a 'posts_data' parameter containing a JSON-encoded object. By setting the 'title_tag' property within that JSON object to a payload like 'img src=x onerror=alert(document.domain)', the attacker causes the server to echo this unsanitized string directly into the HTML response as a tag name, resulting in script execution when the browser renders the response.

Check if your site is affected.

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