Strong Testimonials <= 3.2.21 - Authenticated (Contributor+) Stored Cross-Site Scripting via testimonial_view Shortcode
Description
The Strong Testimonials plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's testimonial_view shortcode in all versions up to, and including, 3.2.21 due to insufficient input sanitization and output escaping on user supplied attributes. 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
<=3.2.21What Changed in the Fix
Changes introduced in v3.2.22
Source Code
WordPress.org SVN## Vulnerability Summary The **Strong Testimonials** plugin (versions <= 3.2.21) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `testimonial_view` shortcode. The vulnerability exists because the plugin fails to sanitize or escape attributes provided by the user in the shortcode befor…
Show full research plan
Vulnerability Summary
The Strong Testimonials plugin (versions <= 3.2.21) is vulnerable to Stored Cross-Site Scripting (XSS) via the testimonial_view shortcode. The vulnerability exists because the plugin fails to sanitize or escape attributes provided by the user in the shortcode before rendering them in the HTML output or passing them to JavaScript localization functions. Specifically, attributes such as id or view are used to construct HTML elements and JavaScript variable names.
Attack Vector Analysis
- Shortcode:
[testimonial_view] - Vulnerable Attribute:
id(and potentiallyview,class, ortitledepending on the template). - Authentication Level: Authenticated (Contributor or higher). Contributors can create posts and insert shortcodes.
- Preconditions: The plugin must be active. A "View" does not necessarily need to exist for the attribute processing to trigger the vulnerability, but having one ensures the full rendering cycle.
- Payload Location: The payload is stored in the
post_contentof a WordPress post/page.
Code Flow
- Entry Point: A user with
edit_postscapability (Contributor+) creates a post containing the shortcode:[testimonial_view id='<payload>']. - Processing: When the page is viewed, WordPress parses the shortcode. The
Strong_Testimonials_Renderclass processes the attributes. - Attribute Storage: In
includes/class-strong-testimonials-render.php, theset_atts($atts)method stores the user-supplied attributes in$this->view_atts. - Rendering Trigger: The rendering process calls
WPMST()->render->prerender( $atts )andWPMST()->render->view_rendered(). - Vulnerable Sink 1 (JS Variable Name): The
view_rendered()method callsview_rendered_after(), which executes `localize
Summary
The Strong Testimonials plugin is vulnerable to Stored Cross-Site Scripting via the 'id' attribute of the [testimonial_view] shortcode. Authenticated attackers with Contributor-level access can inject malicious payloads that break out of unquoted HTML data attributes, leading to arbitrary JavaScript execution in the context of a victim's browser.
Vulnerable Code
// includes/class-strong-testimonials-render.php @ line 647 public function parse_view( $out, $pairs, $atts ) { // Convert "id" to "view" if ( isset( $atts['id'] ) && $atts['id'] ) { $atts['view'] = $atts['id']; unset( $atts['id'] ); } else { --- // includes/functions-template.php @ line 551 function wpmtst_container_data() { $data_array = apply_filters( 'wpmtst_container_data', WPMST()->atts( 'container_data' ) ); if ( $data_array ) { $data = ''; foreach ( $data_array as $attr => $value ) { $data .= " data-$attr=$value"; } echo esc_attr( $data ); } }
Security Fix
@@ -647,9 +647,15 @@ * @return array */ public function parse_view( $out, $pairs, $atts ) { - // Convert "id" to "view" - if ( isset( $atts['id'] ) && $atts['id'] ) { - $atts['view'] = $atts['id']; + // Convert "id" to "view" - sanitize to integer to prevent attribute breakout (security). + if ( isset( $atts['id'] ) && $atts['id'] !== '' ) { + $raw_id = trim( (string) $atts['id'] ); + $view_id = absint( $raw_id ); + // Reject non-numeric or malformed id (e.g. "1 onmouseover=alert(1)"). + if ( $view_id < 1 || $raw_id !== (string) $view_id ) { + return array_merge( array( 'view_not_found' => 1 ), $atts ); + } + $atts['view'] = $view_id; unset( $atts['id'] ); } else { return array_merge( array( 'view_not_found' => 1 ), $atts ); @@ -551,12 +551,15 @@ function wpmtst_container_data() { $data_array = apply_filters( 'wpmtst_container_data', WPMST()->atts( 'container_data' ) ); - if ( $data_array ) { - $data = ''; + if ( $data_array && is_array( $data_array ) ) { + $parts = array(); foreach ( $data_array as $attr => $value ) { - $data .= " data-$attr=$value"; + $attr = sanitize_key( $attr ); + $value = esc_attr( (string) $value ); + $parts[] = sprintf( ' data-%s="%s"', $attr, $value ); } - echo esc_attr( $data ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Each part built from sanitize_key and esc_attr. + echo implode( '', $parts ); } }
Exploit Outline
1. An authenticated attacker with Contributor privileges (or higher) creates or edits a WordPress post. 2. The attacker inserts a `[testimonial_view]` shortcode with a malicious `id` attribute, such as: `[testimonial_view id='1 onmouseover=alert(1)']`. 3. The plugin processes this shortcode via the `Strong_Testimonials_Render` class. The `parse_view` method assigns the malicious payload to the `view` attribute without sanitization. 4. When the page is rendered, the `wpmtst_container_data` function builds the container's HTML attributes. Because the plugin does not quote the attribute values (e.g., `data-view=1 onmouseover=alert(1)`), the space in the payload allows the attacker to inject a new HTML attribute (`onmouseover`). 5. When any user, including an administrator, views the post and interacts with the testimonial container (e.g., hovers their mouse), the injected JavaScript executes.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.