Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : April 23, 2026

CVE-2026-3844: Breeze Cache <= 2.4.4 – Unauthenticated Arbitrary File Upload via fetch_gravatar_from_remote (breeze)

CVE ID CVE-2026-3844
Plugin breeze
Severity Critical (CVSS 9.8)
CWE 434
Vulnerable Version 2.4.4
Patched Version 2.4.5
Disclosed April 21, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-3844:

This vulnerability affects the Breeze Cache plugin for WordPress, specifically the fetch_gravatar_from_remote function in the class-breeze-cache-cronjobs.php file. The flaw allows unauthenticated attackers to upload arbitrary files to the server due to missing file type validation. The vulnerability has a CVSS score of 9.8 and is categorized under CWE-434 (Unrestricted Upload of File with Dangerous Type). Exploitation requires the ‘Host Files Locally – Gravatars’ option to be enabled, which is disabled by default.

The root cause lies in the breeze_replace_gravatar_image function and the fetch_gravatar_from_remote function in /breeze/inc/class-breeze-cache-cronjobs.php. The breeze_replace_gravatar_image function (lines 87-117 in the vulnerable version) extracts URLs from img tags using a regex that could match attacker-controlled text inside other attributes like ‘alt’. It calls fetch_gravatar_from_remote (lines 120-155) which downloads the URL using download_url() and saves the file to the local gravatar cache directory without validating the file type. The code did not check the host of the URL (allowing any domain), did not verify the file extension, and did not run wp_check_filetype_and_ext on the downloaded temporary file before moving it to the final location (wp-content/cache/breeze-extra/gravatars/).

To exploit this, an attacker would need to create a post or comment containing an HTML img tag with a crafted ‘src’ or ‘srcset’ attribute pointing to a malicious file (e.g., a PHP shell) hosted on an attacker-controlled server. When a user (including the attacker themselves) loads the page, the breeze_replace_gravatar_image function parses the img tag and passes the URL to fetch_gravatar_from_remote. The function downloads the attacker’s file and saves it to the gravatars cache directory with the filename derived from the URL path. An attacker can upload a PHP file to the server. The only prerequisite is that the ‘Host Files Locally – Gravatars’ setting is enabled. The attacker does not need authentication, as the replace function runs on any page load when the option is active.

The patch introduces multiple validation layers. First, the regex in breeze_replace_gravatar_image is hardened to require quoted attribute values preceded by whitespace, preventing matching of attacker-controlled text inside other HTML attributes. A host check is added: only URLs pointing to ‘gravatar.com’ or subdomains thereof are processed. A file type check via wp_check_filetype is performed on the local filename before download. The extension must be jpg, png, or gif; if the extension is missing or invalid, ‘.jpg’ is appended. After downloading the temporary file, the patched code runs wp_check_filetype_and_ext to ensure the downloaded content is actually an image. If not, the temporary file is deleted and the original URL is returned. The patch also adds a check in breeze_replace_gravatar_image to avoid replacing the URL if the fetched result is unchanged (i.e., validation failed).

Successful exploitation allows an unauthenticated attacker to upload arbitrary files, including PHP web shells, to the server. This can lead to complete remote code execution, giving the attacker full control over the WordPress site. The attacker can then execute system commands, exfiltrate data, modify or delete files, install backdoors, and pivot to other internal systems. The impact is critical because the uploaded file is stored in a web-accessible directory (wp-content/cache/breeze-extra/gravatars/) and can be accessed directly via HTTP.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/breeze/breeze.php
+++ b/breeze/breeze.php
@@ -2,7 +2,7 @@
 /**
  * Plugin Name: Breeze
  * Description: Breeze is a cache plugin with extensive options to speed up your website. All the options including Varnish Cache are compatible with Cloudways hosting.
- * Version: 2.4.4
+ * Version: 2.4.5
  * Text Domain: breeze
  * Domain Path: /languages
  * Author: Cloudways
@@ -37,7 +37,7 @@
 	define( 'BREEZE_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
 }
 if ( ! defined( 'BREEZE_VERSION' ) ) {
-	define( 'BREEZE_VERSION', '2.4.4' );
+	define( 'BREEZE_VERSION', '2.4.5' );
 }
 if ( ! defined( 'BREEZE_SITEURL' ) ) {
 	define( 'BREEZE_SITEURL', get_site_url() );
--- a/breeze/inc/class-breeze-cache-cronjobs.php
+++ b/breeze/inc/class-breeze-cache-cronjobs.php
@@ -87,17 +87,21 @@
 	 * @return string The updated HTML content with replaced gravatar URLs.
 	 */
 	public function breeze_replace_gravatar_image( string $gravatar ): string {
-		preg_match_all( '/srcset=["']?((?:.(?!["']?s+(?:S+)=|s*/?[>"']))+.)["']?/', $gravatar, $srcset );
-		if ( isset( $srcset[1] ) && isset( $srcset[1][0] ) ) {
-			$url             = explode( ' ', $srcset[1][0] )[0];
+		// Require quoted attribute values preceded by whitespace to prevent
+		// matching attacker-controlled text inside other attributes (e.g. alt).
+		if ( preg_match( '/ssrcset=["']([^"']+)["']/', $gravatar, $srcset_match ) ) {
+			$url             = explode( ' ', trim( $srcset_match[1] ) )[0];
 			$local_gravatars = $this->fetch_gravatar_from_remote( $url );
-			$gravatar        = str_replace( $url, $local_gravatars, $gravatar );
+			if ( $local_gravatars !== $url ) {
+				$gravatar = str_replace( $url, $local_gravatars, $gravatar );
+			}
 		}
-		preg_match_all( '/src=["']?((?:.(?!["']?s+(?:S+)=|s*/?[>"']))+.)["']?/', $gravatar, $src );
-		if ( isset( $src[1] ) && isset( $src[1][0] ) ) {
-			$url             = explode( ' ', $src[1][0] )[0];
+		if ( preg_match( '/ssrc=["']([^"']+)["']/', $gravatar, $src_match ) ) {
+			$url             = explode( ' ', trim( $src_match[1] ) )[0];
 			$local_gravatars = $this->fetch_gravatar_from_remote( $url );
-			$gravatar        = str_replace( $url, $local_gravatars, $gravatar );
+			if ( $local_gravatars !== $url ) {
+				$gravatar = str_replace( $url, $local_gravatars, $gravatar );
+			}
 		}
 		if ( ! is_string( $gravatar ) ) {
 			$gravatar = '';
@@ -120,31 +124,55 @@
 		if ( empty( $url ) ) {
 			return '';
 		}
-		$blog_id             = $this->get_blog_id();
-		$local_gravatar_name = basename( wp_parse_url( $url, PHP_URL_PATH ) );
-		$saved_gravatar      = $this->check_for_content( 'gravatars', $local_gravatar_name );
+
+		$host = strtolower( (string) wp_parse_url( $url, PHP_URL_HOST ) );
+		if ( 'gravatar.com' !== $host && '.gravatar.com' !== substr( $host, -13 ) ) {
+			return $url;
+		}
+
+		$blog_id        = $this->get_blog_id();
+		$gravatar_name  = basename( wp_parse_url( $url, PHP_URL_PATH ) );
+		$filetype       = wp_check_filetype( $gravatar_name );
+		$allowed_images = array( 'image/jpeg', 'image/png', 'image/gif' );
+
+		if ( ! empty( $filetype['type'] ) && ! in_array( $filetype['type'], $allowed_images, true ) ) {
+			return $url;
+		}
+
+		if ( empty( $filetype['ext'] ) || ! in_array( $filetype['type'], $allowed_images, true ) ) {
+			$gravatar_name .= '.jpg';
+		}
+
+		$saved_gravatar = $this->check_for_content( 'gravatars', $gravatar_name );
 		if ( ! empty( $saved_gravatar ) ) {
 			return $saved_gravatar;
 		}
+
 		$wp_filesystem       = breeze_get_filesystem();
 		$gravatar_local_path = $this->get_local_extra_cache_directory( 'gravatars' );
-		$gravatar_name       = basename( wp_parse_url( $url, PHP_URL_PATH ) );
+
 		if ( ! file_exists( $gravatar_local_path . $gravatar_name ) ) {
-			// Making sure the download_url functions is loaded.
 			if ( ! function_exists( 'download_url' ) ) {
 				require_once wp_normalize_path( ABSPATH . '/wp-admin/includes/file.php' );
 			}
-			// Downloads a URL to a local temporary file using the WordPress HTTP API.
+
 			$temp_gravatar = download_url( $url );
-			if ( ! is_wp_error( $temp_gravatar ) ) {
-				// Move the file to the breeze gravatar cache folder.
-				$is_saved = $wp_filesystem->move( $temp_gravatar, $gravatar_local_path . $gravatar_name, true ); // overwriting the destination file.
-				if ( ! $is_saved ) {
-					// if the download and save did not work, return the original url.
-					return $url;
-				}
+			if ( is_wp_error( $temp_gravatar ) ) {
+				return $url;
+			}
+
+			$file_check = wp_check_filetype_and_ext( $temp_gravatar, $gravatar_name );
+			if ( empty( $file_check['type'] ) || 0 !== strpos( $file_check['type'], 'image/' ) ) {
+				@unlink( $temp_gravatar );
+				return $url;
+			}
+
+			$is_saved = $wp_filesystem->move( $temp_gravatar, $gravatar_local_path . $gravatar_name, true );
+			if ( ! $is_saved ) {
 				@unlink( $temp_gravatar );
+				return $url;
 			}
+			@unlink( $temp_gravatar );
 		}

 		return content_url( '/cache/breeze-extra/gravatars/' . $blog_id . $gravatar_name );

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@unconditionalMatch" "id:20263844,phase:2,deny,status:403,msg:'Atomic Edge - CVE-2026-3844 blocked - placeholder only no precise virtual patch possible',severity:CRITICAL,tag:CVE-2026-3844"

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School