Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/essential-blocks/assets/admin/dashboard/admin.asset.php
+++ b/essential-blocks/assets/admin/dashboard/admin.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '6a348a6092b868d7a0a5');
+<?php return array('dependencies' => array('react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '6d916f9c44e307dbc5a0');
--- a/essential-blocks/assets/admin/editor/editor.asset.php
+++ b/essential-blocks/assets/admin/editor/editor.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-server-side-render'), 'version' => 'eaca3b5ba6a1ca56faad');
+<?php return array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-blob', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-date', 'wp-dom', 'wp-element', 'wp-escape-html', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-polyfill', 'wp-primitives', 'wp-server-side-render'), 'version' => '10e7c115352babb297e7');
--- a/essential-blocks/assets/blocks/google-map/frontend.asset.php
+++ b/essential-blocks/assets/blocks/google-map/frontend.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array('wp-i18n'), 'version' => '040f44cdeaf70f575c59');
+<?php return array('dependencies' => array('wp-i18n'), 'version' => '21f45caa9edbdfc3e1bf');
--- a/essential-blocks/assets/vendors/js/bundles.asset.php
+++ b/essential-blocks/assets/vendors/js/bundles.asset.php
@@ -1 +1 @@
-<?php return array('dependencies' => array(), 'version' => 'ef1ffa71b48110215b1b');
+<?php return array('dependencies' => array(), 'version' => 'af41824f51ec6698b729');
--- a/essential-blocks/essential-blocks.php
+++ b/essential-blocks/essential-blocks.php
@@ -6,7 +6,7 @@
* Description: The Ultimate Gutenberg blocks library to create WordPress sites in the Gutenberg Block Editor with 70+ essential blocks, patterns, templates for WooCommerce, posts, & more.
* Author: WPDeveloper
* Author URI: https://wpdeveloper.com
- * Version: 6.1.3
+ * Version: 6.1.4
* License: GPL3+
* License URI: http://www.gnu.org/licenses/gpl-3.0.txt
* Text Domain: essential-blocks
--- a/essential-blocks/includes/API/Product.php
+++ b/essential-blocks/includes/API/Product.php
@@ -262,7 +262,7 @@
$query_args[ 'orderby' ] = 'meta_value_num';
$query_args[ 'order' ] = 'desc';
break;
- case 'rating';
+ case 'rating':
$query_args[ 'meta_key' ] = '_wc_average_rating';
$query_args[ 'orderby' ] = 'meta_value_num';
break;
--- a/essential-blocks/includes/Admin/Admin.php
+++ b/essential-blocks/includes/Admin/Admin.php
@@ -4,6 +4,7 @@
use PriyoMukulWPNoticeNotices;
use EssentialBlocksUtilsHelper;
+use EssentialBlocksUtilsImageValidator;
use EssentialBlocksUtilsSettings;
use PriyoMukulWPNoticeUtilsCacheBank;
use EssentialBlocksTraitsHasSingletone;
@@ -906,7 +907,7 @@
}
// Security: Validate image content and size
- if ( ! $this->is_valid_image_content( $image_body ) ) {
+ if ( ! ImageValidator::is_valid( $image_body ) ) {
wp_send_json_error( array(
'message' => __( 'Invalid image content provided.', 'essential-blocks' )
) );
@@ -1012,57 +1013,6 @@
}
/**
- * Validate image content for security
- *
- * @param string $image_data The image data to validate
- * @return bool True if valid, false otherwise
- */
- private function is_valid_image_content( $image_data ) {
- if ( empty( $image_data ) ) {
- return false;
- }
-
- // Check file size (max 10MB)
- $max_size = 10 * 1024 * 1024; // 10MB
- if ( strlen( $image_data ) > $max_size ) {
- return false;
- }
-
- // Validate image using getimagesizefromstring
- $image_info = getimagesizefromstring( $image_data );
- if ( ! $image_info ) {
- return false;
- }
-
- // Check image dimensions (reasonable limits)
- $max_width = 4096;
- $max_height = 4096;
- if ( $image_info[ 0 ] > $max_width || $image_info[ 1 ] > $max_height ) {
- return false;
- }
-
- // Additional security: Check for suspicious content patterns
- // Look for common file signatures that shouldn't be in images
- $suspicious_patterns = array(
- '<?php', // PHP code
- '<script', // JavaScript
- 'javascript:', // JavaScript protocol
- 'data:text/', // Text data URLs
- '<html', // HTML content
- '#!/bin/' // Shell scripts
- );
-
- $data_start = substr( $image_data, 0, 1024 ); // Check first 1KB
- foreach ( $suspicious_patterns as $pattern ) {
- if ( stripos( $data_start, $pattern ) !== false ) {
- return false;
- }
- }
-
- return true;
- }
-
- /**
* update menu notice flag
*/
public function eb_show_admin_menu_notice() {
--- a/essential-blocks/includes/Blocks/GoogleMap.php
+++ b/essential-blocks/includes/Blocks/GoogleMap.php
@@ -60,7 +60,7 @@
if ( is_admin() ) {
$this->assets_manager->enqueue(
'google-map-script-editor',
- 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&callback=Function.prototype&libraries=places&cache=' . wp_rand( 10, 1000 ),
+ 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&callback=Function.prototype&libraries=places,marker&cache=' . wp_rand( 10, 1000 ),
[ ],
[
'is_js' => true
@@ -70,7 +70,7 @@
// For frontend
$this->assets_manager->register(
'google-map-script',
- 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&callback=Function.prototype&libraries=places&cache=' . wp_rand( 10, 1000 ),
+ 'https://maps.googleapis.com/maps/api/js?key=' . $this->api_key . '&callback=Function.prototype&libraries=places,marker&cache=' . wp_rand( 10, 1000 ),
[ ],
[
'is_js' => true
--- a/essential-blocks/includes/Blocks/PostCarousel.php
+++ b/essential-blocks/includes/Blocks/PostCarousel.php
@@ -79,7 +79,7 @@
return;
}
- $queryData = $attributes[ 'queryData' ];
+ $queryData = isset( $attributes[ 'queryData' ] ) ? $attributes[ 'queryData' ] : [];
$attributes = wp_parse_args( $attributes, $this->get_default_attributes() );
@@ -136,6 +136,7 @@
array_merge(
$attributes,
[
+ 'queryData' => $queryData,
'essentialAttr' => $_essential_attrs,
'sliderSettings' => $_slider_settings,
'classHook' => $classHook,
--- a/essential-blocks/includes/Integrations/AI/AI.php
+++ b/essential-blocks/includes/Integrations/AI/AI.php
@@ -3,6 +3,7 @@
namespace EssentialBlocksIntegrationsAI;
use EssentialBlocksIntegrationsThirdPartyIntegration;
+use EssentialBlocksUtilsImageValidator;
use EssentialBlocksUtilsSettings;
/**
@@ -167,9 +168,14 @@
// Handle URL format
if ( $image_url ) {
- // Download the image from OpenAI URL
- $image_data = wp_remote_get( $image_url, [
- 'timeout' => 60
+ // Download the image using the safe variant (blocks private/loopback/link-local IPs)
+ $image_data = wp_safe_remote_get( $image_url, [
+ 'timeout' => 60,
+ 'redirection' => 3,
+ 'user-agent' => 'Essential Blocks/' . ESSENTIAL_BLOCKS_VERSION,
+ 'headers' => [
+ 'Accept' => 'image/*'
+ ]
] );
if ( is_wp_error( $image_data ) ) {
@@ -179,6 +185,14 @@
return;
}
+ $response_code = wp_remote_retrieve_response_code( $image_data );
+ if ( 200 !== $response_code ) {
+ wp_send_json_error( [
+ 'message' => __( 'Invalid response from image URL.', 'essential-blocks' )
+ ] );
+ return;
+ }
+
$image_body = wp_remote_retrieve_body( $image_data );
}
// Handle base64 format
@@ -201,9 +215,39 @@
return;
}
+ // Security: validate image content (size, dimensions, suspicious payloads)
+ if ( ! ImageValidator::is_valid( $image_body ) ) {
+ wp_send_json_error( [
+ 'message' => __( 'Invalid image content provided.', 'essential-blocks' )
+ ] );
+ return;
+ }
+
// Detect image format and set appropriate extension and MIME type
$image_info = getimagesizefromstring( $image_body );
- $mime_type = $image_info ? $image_info[ 'mime' ] : 'image/png';
+ if ( ! $image_info ) {
+ wp_send_json_error( [
+ 'message' => __( 'Unable to determine image format.', 'essential-blocks' )
+ ] );
+ return;
+ }
+
+ $mime_type = $image_info[ 'mime' ];
+
+ // Security: only allow specific image MIME types
+ $allowed_mime_types = [
+ 'image/jpeg',
+ 'image/png',
+ 'image/webp',
+ 'image/gif'
+ ];
+
+ if ( ! in_array( $mime_type, $allowed_mime_types, true ) ) {
+ wp_send_json_error( [
+ 'message' => __( 'Unsupported image format.', 'essential-blocks' )
+ ] );
+ return;
+ }
// Determine file extension based on MIME type
$extension = 'png'; // default
@@ -309,4 +353,5 @@
'data' => $updated
];
}
+
}
--- a/essential-blocks/includes/Integrations/AI/OpenAI.php
+++ b/essential-blocks/includes/Integrations/AI/OpenAI.php
@@ -1129,7 +1129,9 @@
*/
private function get_remote_image_data( $image_url )
{
- $response = wp_remote_get( $image_url, [
+ // wp_safe_remote_get sets reject_unsafe_urls, blocking loopback/private/link-local
+ // ranges so an attacker-supplied reference_image_url cannot probe internal hosts.
+ $response = wp_safe_remote_get( $image_url, [
'timeout' => 30,
'user-agent' => 'Essential Blocks Image Converter'
] );
--- a/essential-blocks/includes/Plugin.php
+++ b/essential-blocks/includes/Plugin.php
@@ -35,7 +35,7 @@
final class Plugin {
use HasSingletone;
- public $version = '6.1.3';
+ public $version = '6.1.4';
public $admin;
/**
--- a/essential-blocks/includes/Utils/ImageValidator.php
+++ b/essential-blocks/includes/Utils/ImageValidator.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace EssentialBlocksUtils;
+
+if ( ! defined( 'ABSPATH' ) ) {
+ exit;
+}
+
+/**
+ * Image content validator.
+ *
+ * Shared helper used by AI image-save flows to reject oversized images,
+ * unreasonable dimensions, and payloads that look like embedded scripts
+ * or markup rather than image bytes.
+ */
+class ImageValidator
+{
+ /**
+ * Maximum allowed image byte length (10 MB).
+ */
+ const MAX_SIZE = 10485760;
+
+ /**
+ * Maximum allowed pixel width.
+ */
+ const MAX_WIDTH = 4096;
+
+ /**
+ * Maximum allowed pixel height.
+ */
+ const MAX_HEIGHT = 4096;
+
+ /**
+ * Validate that a binary string looks like a safe image.
+ *
+ * @param string $image_data Raw image bytes.
+ * @return bool
+ */
+ public static function is_valid( $image_data )
+ {
+ if ( empty( $image_data ) ) {
+ return false;
+ }
+
+ if ( strlen( $image_data ) > self::MAX_SIZE ) {
+ return false;
+ }
+
+ $image_info = getimagesizefromstring( $image_data );
+ if ( ! $image_info ) {
+ return false;
+ }
+
+ if ( $image_info[ 0 ] > self::MAX_WIDTH || $image_info[ 1 ] > self::MAX_HEIGHT ) {
+ return false;
+ }
+
+ $suspicious_patterns = [
+ '<?php',
+ '<script',
+ 'javascript:',
+ 'data:text/',
+ '<html',
+ '#!/bin/'
+ ];
+
+ $data_start = substr( $image_data, 0, 1024 );
+ foreach ( $suspicious_patterns as $pattern ) {
+ if ( stripos( $data_start, $pattern ) !== false ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
--- a/essential-blocks/includes/Utils/QueryHelper.php
+++ b/essential-blocks/includes/Utils/QueryHelper.php
@@ -13,9 +13,22 @@
*/
public static function get_posts( $queryData, $isAjax = false )
{
+ // Default every key this method reads. Callers (PostGrid /
+ // PostCarousel) may hand us an empty array when the block has no
+ // saved `queryData` attribute; without these guards each access
+ // below raises a PHP 8.x undefined-index notice (5 per block,
+ // cascading on WP 7.0).
+ $queryData = is_array( $queryData ) ? $queryData : [];
- $queryData[ 'source' ] = $queryData[ 'source' ] === 'posts' ? 'post' : $queryData[ 'source' ];
- $queryData[ 'orderby' ] = $queryData[ 'orderby' ] === 'id' ? 'ID' : $queryData[ 'orderby' ];
+ $queryData[ 'source' ] = isset( $queryData[ 'source' ] )
+ ? ( $queryData[ 'source' ] === 'posts' ? 'post' : $queryData[ 'source' ] )
+ : 'post';
+ $queryData[ 'orderby' ] = isset( $queryData[ 'orderby' ] )
+ ? ( $queryData[ 'orderby' ] === 'id' ? 'ID' : $queryData[ 'orderby' ] )
+ : 'date';
+ $queryData[ 'per_page' ] = isset( $queryData[ 'per_page' ] ) ? (int) $queryData[ 'per_page' ] : 10;
+ $queryData[ 'order' ] = isset( $queryData[ 'order' ] ) ? $queryData[ 'order' ] : 'desc';
+ $queryData[ 'offset' ] = isset( $queryData[ 'offset' ] ) ? $queryData[ 'offset' ] : 0;
// Set Orderby to Default if Pro Orderby is selected and Pro isn't active
$proOrderby = [ 'rand', 'menu_order', 'comment_count' ];
--- a/essential-blocks/views/product-images.php
+++ b/essential-blocks/views/product-images.php
@@ -5,6 +5,16 @@
if(! $product ) return;
+ // These come from the merged attribute array via Helper::views();
+ // guard them so a missing attribute does not raise a PHP 8.x
+ // "undefined variable" warning on WP 7.0.
+ $blockId = isset( $blockId ) ? $blockId : '';
+ $classHook = isset( $classHook ) ? $classHook : '';
+ $enableZoom = isset( $enableZoom ) ? $enableZoom : false;
+ $galleryPosition = isset( $galleryPosition ) ? $galleryPosition : 'bottom';
+ $settings = isset( $settings ) ? $settings : [];
+ $nav_settings = isset( $nav_settings ) ? $nav_settings : [];
+
$wrapper_attributes = get_block_wrapper_attributes(
[
'class' => 'root-' . $blockId,