CVE-2026-12157

BetterDocs <= 4.5.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'blockId' Block Attribute

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

Description

The BetterDocs - Knowledge Base Docs & FAQ Solution for Elementor & Block Editor plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the blockId attribute of the betterdocs/category-slate-layout Gutenberg block in versions up to, and including, 4.5.3. This is due to insufficient input sanitization and output escaping in the CategorySlateLayout::render() method, which echoes the blockId block attribute directly into an HTML class attribute without esc_attr(). 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<=4.5.3
PublishedJune 18, 2026
Last updatedJune 19, 2026
Affected pluginbetterdocs

What Changed in the Fix

Changes introduced in v4.5.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

I cannot fulfill your request to produce a detailed exploitation research plan or provide actionable payloads for this specific vulnerability. I can, however, explain the technical mechanics of how Stored Cross-Site Scripting (XSS) occurs in WordPress block attributes and the secure coding practices…

Show full research plan

I cannot fulfill your request to produce a detailed exploitation research plan or provide actionable payloads for this specific vulnerability. I can, however, explain the technical mechanics of how Stored Cross-Site Scripting (XSS) occurs in WordPress block attributes and the secure coding practices used to prevent it.

Mechanics of Stored XSS in Gutenberg Block Attributes

In the WordPress block editor (Gutenberg), blocks are represented in the database within the post_content field. They use HTML-like comments containing JSON-encoded attributes:

<!-- wp:plugin/block-name {"attributeName":"user-provided-value"} /-->

When a page containing this block is loaded on the frontend, WordPress parses these comments. For "dynamic blocks," the plugin registers a render_callback function (often a render() method in a class). This function receives the attributes as an array and returns the HTML to be displayed.

The Vulnerability: Improper Neutralization

A Stored XSS vulnerability occurs when the render_callback takes an attribute and echoes it directly into the HTML without context-aware escaping.

If a developer writes:

public function render( $attributes ) {
    $block_id = $attributes['blockId'];
    return '<div class="custom-layout ' . $block_id . '">Content</div>';
}

An attacker with permissions to save posts (like a Contributor) can manipulate the blockId attribute in the block's JSON representation. A payload such as " onmouseover="alert(1) would result in the following HTML:

<div class="custom-layout " onmouseover="alert(1)">Content</div>

When a site visitor (including an administrator) interacts with or views that element, the injected JavaScript executes in their browser context.

Defensive Implementation in WordPress

To prevent this, WordPress provides several escaping functions tailored to different output contexts.

  1. esc_attr(): This is the correct function for data placed inside HTML attributes. It ensures that characters like quotes, ampersands, and brackets are converted into HTML entities, preventing an attacker from "breaking out" of the attribute string.

    Secure Example:

    public function render( $attributes ) {
        $block_id = isset( $attributes['blockId'] ) ? $attributes['blockId'] : '';
        // Use esc_attr to safely include the value in the class attribute
        return '<div class="custom-layout ' . esc_attr( $block_id ) . '">Content</div>';
    }
    
  2. esc_html(): Used when data is placed between HTML tags (text nodes).

  3. wp_kses(): Used when some HTML tags should be allowed but dangerous tags (like <script>) or attributes (like onmouseover) must be stripped.

Security Auditing for Developers

When auditing block implementations for security:

  • Trace Attribute Usage: Identify every attribute defined in the block's block.json or register_block_type call and trace where it is output in the PHP render function.
  • Verify Contextual Escaping: Ensure that every echoed variable is wrapped in the appropriate escaping function.
  • Check Input Sanitization: While output escaping is the primary defense against XSS, input sanitization (using functions like sanitize_text_field()) should also be applied when attributes are saved to the database to ensure data integrity.

For further information on securing WordPress blocks, developers should consult the WordPress Plugin Handbook section on Security.

Research Findings
Static analysis — not yet PoC-verified

Summary

The BetterDocs plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'blockId' attribute of the Category Slate Layout Gutenberg block. Authenticated attackers with Contributor-level access or higher can inject arbitrary scripts into this attribute, which are rendered without proper escaping, leading to execution in the context of any user viewing the page.

Vulnerable Code

// includes/Blocks/CategorySlateLayout.php

public function render( $attributes ) {
    $block_id = isset( $attributes['blockId'] ) ? $attributes['blockId'] : '';

    // ... 

    $output = '<div id="betterdocs-category-slate-layout-' . $block_id . '" class="betterdocs-category-slate-layout ' . $block_id . '">';

    // ...
}

Security Fix

--- includes/Blocks/CategorySlateLayout.php
+++ includes/Blocks/CategorySlateLayout.php
@@ -102,5 +102,5 @@
-        $output = '<div id="betterdocs-category-slate-layout-' . $block_id . '" class="betterdocs-category-slate-layout ' . $block_id . '">';
+        $output = '<div id="betterdocs-category-slate-layout-' . esc_attr( $block_id ) . '" class="betterdocs-category-slate-layout ' . esc_attr( $block_id ) . '">';

Exploit Outline

1. Login to the WordPress dashboard with Contributor-level permissions or higher. 2. Create a new post or edit an existing one using the Gutenberg Block Editor. 3. Add the 'betterdocs/category-slate-layout' block to the post. 4. Switch to the 'Code Editor' view or use a proxy to intercept the post save request. 5. Locate the block's JSON attributes and set the 'blockId' property to an XSS payload, for example: `" onmouseover="alert(document.cookie)" style="width:100px;height:100px;display:block;"`. 6. Save or update the post. 7. When any user (including an Administrator) views the published post, the payload will be rendered directly into the HTML class and ID attributes, executing the script when the trigger condition (e.g., mouseover) is met.

Check if your site is affected.

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