
PoC RCE per CVE-2026-111497 relativo a una Docker Official Image bloccata di WordPress
Questa PoC source-only riproduce l'esecuzione di comandi non autenticata contro una specifica Docker Official Image bloccata per WordPress.
Una singola richiesta pingback.ping fa sì che WordPress recuperi un URL controllato dall'attaccante due volte durante la stessa richiesta PHP. La prima risposta corrompe un handler PHP tramite Brotli. L'header del retry avvia Bash e restituisce la flag del lab.
Il profilo è specifico per il target. Non avanza alcuna pretesa riguardo alle immagini WordPress attuali, ad altri digest o a un errore di memoria in WordPress Core.
ATTENZIONE: esegui questa PoC solo nel lab incluso, su un sistema di tua proprietà. Contiene un payload RCE funzionante.
/flag.txt aggiornato e la flag viene restituita nella risposta XML-RPC.sh → bash → sleep.| Voce | Valore |
|---|---|
| Target | docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5 |
| Image ID | c64dd89c00079939f18d70489e7c3e30cb071e8581824ab2de688f9f4d70d076 |
| Piattaforma | Linux amd64, Debian 13.7 |
| Applicazione | WordPress 7.1.3, Apache 2.4.69 prefork |
| Runtime | PHP 8.3.36 NTS |
| Decoder | libbrotli1:amd64 1.1.0-2+b7 |
Questo manifest Linux/amd64 era selezionato da wordpress:latest al momento della pubblicazione. L'attuale tag latest non lo seleziona più.
Il lab esegue questa Docker Official Image non modificata con Podman rootless. Non inietta alcuna libreria Brotli.
CVE-2026-111497 fornisce la corruzione del decoder. WordPress fornisce il percorso di fetch non autenticato tramite pingback.ping.
Il flusso malformato copia sei byte dal puntatore dell'handler system attivo nell'handler strtolower. Entrambi i puntatori usano la stessa base libphp.so caricata via ASLR. Pertanto, il profilo non necessita di un leak di indirizzo assoluto.
Il profilo dipende comunque dall'esatta combinazione di Brotli, PHP, glibc, cURL, Requests, Apache, allocatore e cronologia delle richieste. La prima risposta usa un header di 161 byte e un body di 23.069.816 byte. L'origine invia 23.069.752 byte, attende un secondo e invia i 64 byte finali.
Il decoder restituisce un errore dopo la corruzione. Requests esegue il retry senza decodifica del contenuto dopo CURLE_BAD_CONTENT_ENCODING. Il nome del suo primo header raggiunge l'handler modificato e avvia il comando Bash fisso.
Il percorso applicativo richiede queste condizioni:
/xmlrpc.php è raggiungibile e pingback.ping è abilitato.Il lab crea queste condizioni senza plugin né strumentazione lato vittima.
generate_payloads.py è la fonte autorevole dei payload. Il repository non traccia alcun binario payload precompilato.
Il generatore usa solo la libreria standard di Python. Funziona offline e non invia traffico né esegue comandi. Definisce le costanti del profilo e le fasi di serializzazione da RFC 7932.
verify.sh rigenera tutti i frammenti. Verifica le loro dimensioni, gli hash, gli offset di bit intermedi e la relazione del selettore a un bit.
L'host testato usava Ubuntu 24.04.4 sotto WSL2 su amd64. Usava Podman rootless 4.9.3, Bash 5.2.21 e Python 3.12.3.
I requisiti minimi sono Linux amd64, Podman rootless, Bash, Python 3.8 o successivo, modalità ASLR 2 e i normali strumenti utente GNU. Mantieni libere la porta 18170 e la subnet 93.184.218.0/24, oppure scegli alternative. Un host con SELinux in modalità enforcing può rifiutare i bind mount.
Prima di eseguire il lab, scarica le immagini immutabili:
podman pull docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5
podman pull docker.io/library/wordpress@sha256:65d5f185a6f539803a177ea9cdd5ad632ae24427bea114447e67a6fcd5d8bfe8
podman pull docker.io/library/mariadb@sha256:33a6653ab666774f48818120045f9367a81cd4c54c027412fe294a7d1274e2dc
Rendi eseguibili gli script, poi verifica il pacchetto sorgente:
chmod +x generate_payloads.py verify.sh run.sh
./verify.sh
Esegui ogni modalità con un nuovo tag:
| Modalità | Comando | Risultato richiesto |
|---|---|---|
| Positiva | ./run.sh standard | Restituisce il flag e stampa PoC positive: |
| Osservabile | ./run.sh observable | Registra anche i processi visibili bash e sleep |
| Selettore | ./run.sh selector | Riprova ma mantiene il flag fuori dalla risposta |
Il selettore modifica un bit all'offset di byte 1066. È un controllo causale sulla stessa immagine, non un controllo a decoder fisso.
Gli argomenti opzionali sono la modalità, il tag, la porta host e il terzo ottetto di rete:
./run.sh standard trial2 18171 219
Il runner scrive le evidenze in runs/<tag>/. I file principali sono:
VERDICT.txt: conteggi dei risultati e delle richiesteclient-request.raw e client-response.raw: dati wire in ingressoorigin-evidence/origin-events.json: entrambe le richieste in uscita e gli header di retryoracle-match.txt: la corrispondenza del flag nella rispostapayloads/identity.json: metadati del profilo generatoMANIFEST.sha256: hash di tutti i file di evidenzaIl runner utilizza una rete Podman interna e pubblica WordPress solo su 127.0.0.1. Il client non accetta alcun indirizzo di destinazione. L'origin non accetta input di comandi. Il comando fisso legge solo il flag sintetico.
Il runner rimuove il valore del flag dall'ambiente Apache prima che Apache si avvii. Crea inoltre credenziali nuove e rimuove solo le risorse dell'esecuzione corrente.
La suddivisione della risposta e la cronologia dei processi sono input dell'exploit. Se un'esecuzione fallisce, ripetere il profilo invariato con un nuovo tag. Non modificare indirizzi o byte del payload dopo un'esecuzione fallita.
Questo pacchetto non garantisce la portabilità tra digest delle immagini, build di PHP, architetture, allocatori o schedulazioni delle risposte. Un target diverso può interrompere l'esecuzione o causare solo un crash.
Upstream ha corretto i rami Brotli mantenuti nelle versioni 1.0.10, 1.1.1 e 1.2.1. I fornitori delle distribuzioni possono applicare il backport della correzione. Utilizzare l'advisory del fornitore e il changelog del pacchetto, non solo la versione visualizzata.