Sticky <= 2.5.6 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'readmoretext' Shortcode Attribute
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:NTechnical Details
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_contentof 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)
- Registration: The plugin registers the shortcode in the
inithook:add_shortcode('cvmh-sticky', 'cvmh_sticky_front_render'); - Attribute Handling: The
cvmh_sticky_front_render($atts)function receives the user-provided attributes. - Processing: The code likely extracts the
readmoretextattribute:$read_more = isset($atts['readmoretext']) ? $atts['readmoretext'] : 'Read More'; - Filtering: The value may be passed through
apply_filters(), which does not provide security sanitization. - Sink: The value is concatenated into an HTML string:
$output .= '<div class="cvmh-read-more">' . $read_more . '</div>'; - Return: The unescaped
$outputis 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)
- Identify Nonce: The WordPress REST API uses a nonce typically localized in the
wpApiSettingsobject. - Acquisition:
- Navigate to the WordPress Dashboard (
/wp-admin/) as a Contributor. - Execute via
browser_eval:window.wpApiSettings.nonce.
- Navigate to the WordPress Dashboard (
- Post Creation: Use the acquired nonce in the
X-WP-Nonceheader to create a post containing the payload.
Legacy Post Editor Method
- Identify Nonce: The standard post editor uses a hidden field named
_wpnonce. - Acquisition:
- Navigate to
/wp-admin/post-new.php. - Execute via
browser_eval:document.querySelector('#_wpnonce').value.
- Navigate to
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/jsonX-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
- Obtain the ID of the created post from the response.
- Navigate to the preview URL:
https://[target]/?p=[POST_ID]&preview=true. - Observe the execution of
alert(document.domain).
6. Test Data Setup
- Plugin Installation: Ensure the Sticky plugin (version <= 2.5.6) is active.
- User Creation: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123
- 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>']
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
@@ -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.