WPZOOM Addons for Elementor – Starter Templates & Widgets <= 1.3.4 - Unauthenticated Reflected Cross-Site Scripting via 'title_tag' Parameter
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:NTechnical Details
<=1.3.4What Changed in the Fix
Changes introduced in v1.3.5
Source Code
WordPress.org SVN# 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-encodedposts_dataPOST parameter). - Authentication: None required.
- Preconditions:
- A valid WordPress nonce for the action
wpz_posts_grid_load_more. - At least one published post must exist on the site (to satisfy the
WP_Querycondition and trigger the inclusion of the layout file).
- A valid WordPress nonce for the action
3. Code Flow
- Entry Point: A
POSTrequest is sent toadmin-ajax.phpwithaction=wpz_posts_grid_load_more. - Nonce Verification:
includes/wpzoom-elementor-ajax-posts-grid.php(Line 81) callswp_verify_nonce( $_POST['nonce'], 'wpz_posts_grid_load_more' ). - Data Processing:
$_POST['posts_data']is sanitized withsanitize_text_field()and thenjson_decode()ed into an array$data(Lines 85-86).self::$settingsis populated with this array (Line 88).
- Query Execution:
WP_Queryis executed using parameters from$data(Lines 90-116). - 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). - Sink: The layout file (not provided, but inferred by class logic) calls
$this->render_title(). - Execution:
render_title()(Lines 216-234) retrieves$title_tagfrom$settings. It then echos this value directly into the HTML output:
The comment<<?php echo $title_tag; // WPCS: XSS OK. ?> class="title">// 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.
- 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.
- Identification: The plugin likely localizes the nonce in a variable. Based on common naming conventions in this plugin (like
wpzoom_admin_datain the main file), look for a variable named something likewpz_posts_grid_settings. - Execution:
- Use
wp post createto 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
- Use
5. Exploitation Strategy
- Target:
/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Parameters:
action:wpz_posts_grid_load_morenonce:[EXTRACTED_NONCE]offset:0posts_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 }
- Payload Formulation: The
title_tagvalueimg 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
- Create a Post: Ensure the grid has data to render.
wp post create --post_type=post --post_title="PoC Post" --post_status=publish - Create a Page with Widget: To trigger nonce localization. If the exact Elementor block is unknown, the agent should search the plugin for
add_shortcodeor widget registration names.# Search for widget name grep -r "get_name" includes/widgets/ - 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
alertbox would execute.
8. Verification Steps
- Response Check: Use the
http_requesttool and verify that the response body contains the stringonerror=alert(document.domain). - Path Confirmation: Verify the code path by checking if the plugin's
render_titlefunction is indeed called during the AJAX request by observing the response structure.
9. Alternative Approaches
- Tag Breakout: If the
title_tagis used in multiple places (like the closing tag), tryscript>alert(1)</script. - JSON Encoding: If
sanitize_text_fieldinterferes with the payload, remember thatposts_datais JSON-encoded, so double-escaping or Unicode escapes (\u003c) might be necessary depending on howstripslashesandjson_decodehandle the input. - Different Layouts: The
grid_styleparameter determines which layout file is included. Iflayout-1.phpis missing or doesn't callrender_title, try other integers (1, 2, 3, etc.).
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
@@ -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.