Brizy <= 2.7.23 - Missing Authorization
Description
The Brizy plugin for WordPress is vulnerable to unauthorized access due to a missing capability check on a function in versions up to, and including, 2.7.23. This makes it possible for authenticated attackers, with contributor-level access and above, to perform an unauthorized action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.7.24
Source Code
WordPress.org SVN# Exploitation Research Plan - CVE-2026-32408 (Brizy <= 2.7.23) ## 1. Vulnerability Summary The Brizy Page Builder plugin for WordPress is vulnerable to **Missing Authorization** in several AJAX API endpoints. The plugin implements an abstract API class `Brizy_Admin_AbstractApi` and multiple subcla…
Show full research plan
Exploitation Research Plan - CVE-2026-32408 (Brizy <= 2.7.23)
1. Vulnerability Summary
The Brizy Page Builder plugin for WordPress is vulnerable to Missing Authorization in several AJAX API endpoints. The plugin implements an abstract API class Brizy_Admin_AbstractApi and multiple subclasses (e.g., Brizy_Admin_Blocks_Api, Brizy_Admin_Fonts_Api) that register AJAX actions. While these actions verify a WordPress nonce and an editor version string, they fail to perform any capability checks (e.g., current_user_can()). This allows authenticated users with Contributor-level access and above to perform administrative actions such as creating, updating, or deleting global blocks, layouts, and custom fonts.
2. Attack Vector Analysis
- Endpoint:
wp-admin/admin-ajax.php - AJAX Action:
brizy-delete-saved-block(or any other action registered in theBrizy_Admin_*_Apiclasses). - HTTP Method:
POST - Payload Parameters:
action:brizy-delete-saved-block(derived fromBrizy_Editor::prefix()+self::DELETE_SAVED_BLOCK_ACTION)hash: A valid WordPress nonce for thebrizy-apiaction.version: The current plugin version (e.g.,2.7.23).uid: The unique identifier of the block to delete.
- Required Authentication: Contributor level (
PR:L). - Preconditions: The attacker must be logged in as a Contributor and have access to the Brizy editor interface (which Contributors have for their own posts) to retrieve the required nonce.
3. Code Flow
- Entry Point: The AJAX action is registered in
admin/blocks/api.phpviainitializeApiActions():$pref = 'wp_ajax_' . Brizy_Editor::prefix(); // prefix is 'brizy' add_action( $pref . self::DELETE_SAVED_BLOCK_ACTION, array( $this, 'actionDeleteSavedBlock' ) ); - Abstract Verification: The handler calls
verifyNonce( self::nonce )defined inadmin/abstract-api.php:protected function verifyNonce( $action ) { $version = $this->param( 'version' ); if ( $version !== BRIZY_EDITOR_VERSION ) { ... } // Checks version match $this->checkNonce( $action ); // Verifies 'brizy-api' nonce } - Missing Sink: After
verifyNonce, the code proceeds directly to the functional logic (e.g., deleting a block viaBrizy_Admin_Blocks_Manager) without anycurrent_user_can()check.
4. Nonce Acquisition Strategy
The brizy-api nonce is required for all API calls. It is localized for the Brizy editor.
- Shortcode: Brizy uses a custom editor interface. Creating a post and clicking "Edit with Brizy" loads the necessary scripts.
- Page Creation: Use WP-CLI to create a post the Contributor can edit.
wp post create --post_type=post --post_title="Exploit Page" --post_status=publish --post_author=[CONTRIBUTOR_ID] - Navigation: Navigate to the Brizy editor for that post. The URL format is typically:
wp-admin/post.php?post=[ID]&action=brizy-edit. - Extraction: Use
browser_evalto extract the configuration object. Brizy typically stores its configuration in a global JavaScript object namedBrizyorBrizyLocal.- Target Variable:
window.brizyEditorConfig?.api?.nonceorwindow.Brizy?.config?.api?.nonce. - Version: The version string is also usually available in the same config object:
window.brizyEditorConfig?.version.
- Target Variable:
5. Exploitation Strategy
We will demonstrate the vulnerability by deleting a "Saved Block" created by an Administrator.
- Retrieve Target UID: Use WP-CLI to find a block UID created by the Admin.
- Brizy saved blocks are usually in the
brizy_saved_blockpost type. wp post list --post_type=brizy_saved_block --format=ids- Get the UID from post meta:
wp post meta get [POST_ID] brizy_post_uid
- Brizy saved blocks are usually in the
- Acquire Nonce: As a Contributor, visit the editor and extract the
hash(nonce) andversion. - Execute Exploit: Send a POST request to
admin-ajax.php.
Request Details:
- URL:
http://[TARGET]/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
action=brizy-delete-saved-block&hash=[NONCE]&version=[VERSION]&uid=[TARGET_UID]
6. Test Data Setup
- Administrator Actions:
- Create a saved block in Brizy (this generates a
brizy_saved_blockpost). - Record the
brizy_post_uidof this block.
- Create a saved block in Brizy (this generates a
- Contributor User:
- Create a user with the
contributorrole.
- Create a user with the
- Editor Access:
- Ensure Brizy is enabled for the
postpost type. - Create a post authored by the Contributor.
- Ensure Brizy is enabled for the
7. Expected Results
- The AJAX request should return a JSON success response:
{"success": true, "data": []}. - The
brizy_saved_blockpost associated with the target UID should be deleted or moved to trash. - Unauthorized users (Contributors) can manipulate global Brizy entities they do not own.
8. Verification Steps
- Check Post Existence: Use WP-CLI to verify the block is gone.
wp post list --post_type=brizy_saved_block --post_author=[ADMIN_ID] - Check Database: Verify the post with the specific UID no longer exists in
wp_postsorwp_postmeta.
9. Alternative Approaches
If delete-saved-block fails, try brizy-create-font in Brizy_Admin_Fonts_Api:
- Action:
brizy-create-font - Payload: Requires
id,family, andfonts[]file upload. - Impact: Allows a Contributor to upload arbitrary files to the
wp-content/uploads/brizy/fontsdirectory, which could lead to further exploitation depending on file handling.
Another alternative is brizy-upload-blocks in Brizy_Admin_Blocks_Api:
- Action:
brizy-upload-blocks - Payload: Requires a ZIP file containing block data.
- Impact: Allows mass creation of global blocks and potentially file uploads via the ZIP extraction process.
Summary
The Brizy Page Builder plugin for WordPress is vulnerable to unauthorized access due to missing capability checks in several AJAX API endpoints. This allows authenticated attackers with contributor-level access and above to perform administrative actions such as creating, updating, or deleting global blocks, layouts, and custom fonts.
Vulnerable Code
// admin/abstract-api.php line 24 protected function verifyNonce( $action ) { $version = $this->param( 'version' ); if ( $version !== BRIZY_EDITOR_VERSION ) { Brizy_Logger::instance()->critical( 'Request with invalid editor version', [ editorVersion' => BRIZY_EDITOR_VERSION, 'providedVersion' => $version ] ); $this->error( 400, "Invalid editor version. Please refresh the page and try again" ); } $this->checkNonce( $action ); } --- // admin/blocks/api.php line 577 public function actionDeleteSavedBlock() { $this->verifyNonce( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); } if ( ! $this->param( 'uid' ) ) { $this->error( 400, 'Invalid uid' ); } $bockManager = new Brizy_Admin_Blocks_Manager( Brizy_Admin_Blocks_Main::CP_SAVED ); try { $bockManager->deleteEntity( $this->param( 'uid' ) ); } catch ( Exception $exception ) { $this->error( 400, $exception->getMessage() ); } $this->success( [] ); }
Security Fix
@@ -21,7 +21,14 @@ /** * @param $action */ - protected function verifyNonce( $action ) { + protected function verifyUserCanEdit() { + if (!Brizy_Editor_User::is_user_allowed($this->param('post'))) { + $this->error( 400, "You are not allowed to edit this post" ); + } + } + protected function verifyAuthorization( $action ) { + + $this->verifyUserCanEdit(); $version = $this->param( 'version' ); if ( $version !== BRIZY_EDITOR_VERSION ) { @@ -73,7 +73,7 @@ } public function actionDownloadBlocks() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! $this->param( 'uid' ) ) { $this->error( 400, 'Invalid block uid param' ); } @@ -125,7 +125,7 @@ public function actionUploadBlocks() { try { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! isset( $_FILES['files'] ) ) { $this->error( 400, __( 'Invalid block file' ) ); } @@ -164,7 +164,7 @@ } public function actionGetGlobalBlocks() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); try { $fields = $this->param( 'fields' ) ? $this->param( 'fields' ) : []; $bockManager = new Brizy_Admin_Blocks_Manager( Brizy_Admin_Blocks_Main::CP_GLOBAL ); @@ -180,7 +180,7 @@ } public function actionCreateGlobalBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! $this->param( 'uid' ) ) { $this->error( 400, 'Invalid uid' ); } @@ -256,7 +256,7 @@ } public function actionUpdateGlobalBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); try { if ( ! $this->param( 'uid' ) ) { @@ -343,7 +343,7 @@ } public function actionUpdateGlobalBlocks() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); try { if ( ! current_user_can( 'edit_pages' ) ) { @@ -436,7 +436,7 @@ } public function actionDeleteGlobalBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); } @@ -454,7 +454,7 @@ } public function actionGetSavedBlocks() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); try { $fields = $this->param( 'fields' ) ? $this->param( 'fields' ) : []; $bockManager = new Brizy_Admin_Blocks_Manager( Brizy_Admin_Blocks_Main::CP_SAVED ); @@ -472,7 +472,7 @@ } public function actionGetSavedBlockByUid() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! $this->param( 'uid' ) ) { $this->error( 400, 'Invalid uid' ); } @@ -492,7 +492,7 @@ } public function actionCreateSavedBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); } @@ -532,7 +532,7 @@ } public function actionUpdateSavedBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); } @@ -578,7 +578,7 @@ } public function actionDeleteSavedBlock() { - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); } @@ -604,7 +604,7 @@ public function actionUpdateBlockPositions() { global $wpdb; - $this->verifyNonce( self::nonce ); + $this->verifyAuthorization( self::nonce ); if ( ! current_user_can( 'edit_pages' ) ) { $this->error( 403, 'Unauthorized.' ); }
Exploit Outline
The exploit targets the AJAX endpoints registered by the Brizy plugin which fail to implement authorization checks. 1. Log in as a user with Contributor-level permissions. 2. Navigate to the Brizy editor for a post the contributor is allowed to edit (e.g., /wp-admin/post.php?post=[ID]&action=brizy-edit) to obtain a valid WordPress nonce for the 'brizy-api' action and the current BRIZY_EDITOR_VERSION string from the localized JavaScript configuration (window.brizyEditorConfig). 3. Identify a target object UID (such as a Global Block, Saved Block, or Font UID) that should be restricted to administrators. 4. Send a POST request to /wp-admin/admin-ajax.php with the following parameters: - action: The target Brizy action (e.g., brizy-delete-saved-block or brizy-create-font). - hash: The extracted 'brizy-api' nonce. - version: The current Brizy plugin version. - uid: The identifier of the block or object to manipulate. 5. Because the vulnerable code only verifies the nonce and version but lacks capability checks (current_user_can), the server will execute the administrative action on behalf of the low-privileged user.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.