
PoC RCE pour CVE-2026-111497 concernant une image Docker Official Image épinglée pour WordPress
Ce PoC source-only reproduit une exécution de commande non authentifiée contre une image Docker Official Image pour WordPress épinglée.
Une seule requête pingback.ping amène WordPress à récupérer une URL contrôlée par l'attaquant deux fois au cours de la même requête PHP. La première réponse corrompt un handler PHP via Brotli. L'en-tête de nouvelle tentative démarre Bash et renvoie le flag du lab.
Le profil est spécifique à la cible. Il n'affirme rien sur les images WordPress actuelles, d'autres digests, ou une erreur mémoire dans WordPress Core.
AVERTISSEMENT : N'exécutez ce PoC que dans le lab fourni, sur un système qui vous appartient. Il contient une charge utile RCE fonctionnelle.
/flag.txt frais, et le flag est renvoyé dans la réponse XML-RPC.sh vers bash vers sleep.| Élément | Valeur |
|---|---|
| Cible | docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5 |
| ID de l'image | c64dd89c00079939f18d70489e7c3e30cb071e8581824ab2de688f9f4d70d076 |
| Plateforme | Linux amd64, Debian 13.7 |
| Application | WordPress 7.1.3, Apache 2.4.69 prefork |
| Runtime | PHP 8.3.36 NTS |
| Décodeur | libbrotli1:amd64 1.1.0-2+b7 |
Ce manifeste Linux/amd64 était sélectionné par wordpress:latest au moment de sa publication. Le tag latest actuel ne le sélectionne plus.
Le lab exécute cette Docker Official Image non modifiée avec Podman rootless. Il n'injecte aucune bibliothèque Brotli.
CVE-2026-111497 fournit la corruption du décodeur. WordPress fournit le chemin de récupération non authentifié via pingback.ping.
Le flux malformé copie six octets depuis le pointeur du handler system actif vers le handler strtolower. Les deux pointeurs utilisent la même base libphp.so chargée par ASLR. Par conséquent, le profil n'a pas besoin d'une fuite d'adresse absolue.
Le profil dépend toutefois de l'historique exact de Brotli, PHP, glibc, cURL, Requests, Apache, de l'allocateur et des requêtes. La première réponse utilise un en-tête de 161 octets et un corps de 23 069 816 octets. L'origine envoie 23 069 752 octets, attend une seconde, puis envoie les 64 derniers octets.
Le décodeur renvoie une erreur après la corruption. Requests réessaie sans décodage de contenu après CURLE_BAD_CONTENT_ENCODING. Le nom de son premier en-tête atteint le handler modifié et démarre la commande Bash fixe.
Le chemin applicatif exige les conditions suivantes :
/xmlrpc.php est joignable, et pingback.ping est activé.Le lab crée ces conditions sans plugin ni instrumentation côté victime.
generate_payloads.py est la source de référence des charges utiles. Le dépôt ne suit aucun binaire de charge utile précompilé.
Le générateur n'utilise que la bibliothèque standard de Python. Il fonctionne hors ligne et n'envoie aucun trafic ni n'exécute aucune commande. Il définit les constantes du profil et les étapes de sérialisation à partir de la RFC 7932.
verify.sh régénère tous les fragments. Il vérifie leurs tailles, leurs hachages, les décalages de bits intermédiaires et la relation du sélecteur d'un bit.
L'hôte testé utilisait Ubuntu 24.04.4 sous WSL2 sur amd64. Il utilisait Podman rootless 4.9.3, Bash 5.2.21 et Python 3.12.3.
Les prérequis minimaux sont Linux amd64, Podman rootless, Bash, Python 3.8 ou ultérieur, le mode ASLR 2 et les outils utilisateur GNU standard. Laissez le port 18170 et le sous-réseau 93.184.218.0/24 libres, ou choisissez des alternatives. Un hôte SELinux en mode enforcing peut rejeter les bind mounts.
Avant d'exécuter le lab, récupérez les images immuables :
podman pull docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5
podman pull docker.io/library/wordpress@sha256:65d5f185a6f539803a177ea9cdd5ad632ae24427bea114447e67a6fcd5d8bfe8
podman pull docker.io/library/mariadb@sha256:33a6653ab666774f48818120045f9367a81cd4c54c027412fe294a7d1274e2dc
Rendez les scripts exécutables, puis vérifiez le paquet source :
chmod +x generate_payloads.py verify.sh run.sh
./verify.sh
Exécutez chaque mode avec un nouveau tag :
| Mode | Commande | Résultat attendu |
|---|---|---|
| Positif | ./run.sh standard | Renvoie le flag et affiche PoC positive: |
| Observable | ./run.sh observable | Enregistre également les processus bash et sleep visibles |
| Sélecteur | ./run.sh selector | Réessaie mais garde le flag hors de la réponse |
Le sélecteur modifie un bit à l'offset d'octet 1066. Il s'agit d'un contrôle causal sur la même image, et non d'un contrôle à décodeur fixe.
Les arguments optionnels sont le mode, le tag, le port hôte et le troisième octet réseau :
./run.sh standard trial2 18171 219
Le runner écrit les preuves dans runs/<tag>/. Les principaux fichiers sont :
VERDICT.txt : résultat et nombre de requêtesclient-request.raw et client-response.raw : données réseau entrantesorigin-evidence/origin-events.json : les deux requêtes sortantes et les en-têtes de nouvelle tentativeoracle-match.txt : la correspondance du flag dans la réponsepayloads/identity.json : métadonnées du profil généréMANIFEST.sha256 : hachages de tous les fichiers de preuveLe runner utilise un réseau Podman interne et ne publie WordPress que sur 127.0.0.1. Le client n'accepte aucune adresse cible. L'origine n'accepte aucune entrée de commande. La commande fixe ne lit que le flag synthétique.
Le runner supprime la valeur du flag de l'environnement Apache avant le démarrage d'Apache. Il crée également de nouveaux identifiants et ne supprime que les ressources de l'exécution en cours.
La division de la réponse et l'historique des processus sont des entrées d'exploitation. Si une exécution échoue, répétez le profil inchangé avec un nouveau tag. Ne modifiez pas les adresses ni les octets de la charge utile après une exécution échouée.
Ce paquet n'établit pas de portabilité entre les digests d'images, les builds PHP, les architectures, les allocateurs ou les planifications de réponse. Une cible différente peut arrêter l'exécution ou ne provoquer qu'un plantage.
En amont, les branches Brotli maintenues ont été corrigées dans les versions 1.0.10, 1.1.1 et 1.2.1. Les fournisseurs de distribution peuvent rétroporter le correctif. Utilisez l'avis du fournisseur et le changelog du paquet, pas seulement la version affichée.