
PoC de RCE para CVE-2026-111497 referente a uma imagem oficial do Docker fixada (pinned) para WordPress
Este PoC somente de código-fonte reproduz execução de comandos não autenticada contra uma imagem Docker Official Image fixada para WordPress.
Uma requisição pingback.ping faz o WordPress buscar uma URL do atacante duas vezes durante a mesma requisição PHP. A primeira resposta corrompe um handler PHP por meio do Brotli. O cabeçalho de nova tentativa inicia o Bash e retorna a flag do laboratório.
O perfil é específico do alvo. Ele não faz nenhuma afirmação sobre imagens atuais do WordPress, outros digests ou um erro de memória no WordPress Core.
AVISO: Execute este PoC apenas no laboratório incluído, em um sistema que seja seu. Ele contém um payload de RCE funcional.
/flag.txt novo, e a flag retorna na resposta XML-RPC.sh para bash para sleep.| Item | Valor |
|---|---|
| Alvo | docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5 |
| ID da imagem | c64dd89c00079939f18d70489e7c3e30cb071e8581824ab2de688f9f4d70d076 |
| Plataforma | Linux amd64, Debian 13.7 |
| Aplicação | WordPress 7.1.3, Apache 2.4.69 prefork |
| Runtime | PHP 8.3.36 NTS |
| Decodificador | libbrotli1:amd64 1.1.0-2+b7 |
Este manifesto Linux/amd64 foi selecionado por wordpress:latest no momento da publicação. A tag latest atual não o seleciona mais.
O laboratório executa esta Docker Official Image sem modificações com Podman rootless. Ele não injeta uma biblioteca Brotli.
A CVE-2026-111497 fornece a corrupção do decodificador. O WordPress fornece o caminho de busca não autenticada por meio de pingback.ping.
O fluxo malformado copia seis bytes do ponteiro do handler system ativo para o handler strtolower. Ambos os ponteiros usam a mesma base de libphp.so carregada por ASLR. Portanto, o perfil não precisa de um vazamento de endereço absoluto.
O perfil ainda depende do Brotli, PHP, glibc, cURL, Requests, Apache, alocador e histórico de requisições exatos. A primeira resposta usa um cabeçalho de 161 bytes e um corpo de 23.069.816 bytes. A origem envia 23.069.752 bytes, aguarda um segundo e envia os 64 bytes finais.
O decodificador retorna um erro após a corrupção. O Requests tenta novamente sem decodificação de conteúdo após CURLE_BAD_CONTENT_ENCODING. Seu primeiro nome de cabeçalho alcança o handler modificado e inicia o comando Bash fixo.
O caminho da aplicação exige estas condições:
/xmlrpc.php está acessível e pingback.ping está habilitado.O laboratório cria essas condições sem plugin ou instrumentação no lado da vítima.
generate_payloads.py é a fonte autoritativa dos payloads. O repositório não rastreia binários de payload pré-construídos.
O gerador usa apenas a biblioteca padrão do Python. Ele funciona offline e não envia tráfego nem executa comandos. Ele define as constantes do perfil e os estágios de serialização a partir da RFC 7932.
verify.sh regenera todos os fragmentos. Ele verifica seus tamanhos, hashes, deslocamentos de bits intermediários e a relação do seletor de um bit.
O host testado usou Ubuntu 24.04.4 sob WSL2 em amd64. Ele usou Podman rootless 4.9.3, Bash 5.2.21 e Python 3.12.3.
Os requisitos mínimos são Linux amd64, Podman rootless, Bash, Python 3.8 ou posterior, ASLR modo 2 e ferramentas de usuário GNU padrão. Mantenha a porta 18170 e a sub-rede 93.184.218.0/24 livres, ou selecione alternativas. Um host com SELinux em modo enforcing pode rejeitar os bind mounts.
Antes de executar o laboratório, baixe as imagens imutáveis:
podman pull docker.io/library/wordpress@sha256:806680ba62232c4c300fb9dcc9f89d08e9e8c2bfc59837a1f58716f8648306d5
podman pull docker.io/library/wordpress@sha256:65d5f185a6f539803a177ea9cdd5ad632ae24427bea114447e67a6fcd5d8bfe8
podman pull docker.io/library/mariadb@sha256:33a6653ab666774f48818120045f9367a81cd4c54c027412fe294a7d1274e2dc
Torne os scripts executáveis e, em seguida, verifique o pacote de origem:
chmod +x generate_payloads.py verify.sh run.sh
./verify.sh
Execute cada modo com uma nova tag:
| Modo | Comando | Resultado esperado |
|---|---|---|
| Positivo | ./run.sh standard | Retorna a flag e imprime PoC positive: |
| Observável | ./run.sh observable | Também registra processos bash e sleep visíveis |
| Seletor | ./run.sh selector | Tenta novamente, mas mantém a flag fora da resposta |
O seletor altera um bit no deslocamento de byte 1066. É um controle causal da mesma imagem, não um controle de decodificador fixo.
Os argumentos opcionais são o modo, a tag, a porta do host e o terceiro octeto da rede:
./run.sh standard trial2 18171 219
O runner grava evidências em runs/<tag>/. Os principais arquivos são:
VERDICT.txt: resultado e contagens de requisiçõesclient-request.raw e client-response.raw: dados de rede de entradaorigin-evidence/origin-events.json: ambas as requisições de saída e cabeçalhos de retryoracle-match.txt: a correspondência da flag na respostapayloads/identity.json: metadados do perfil geradoMANIFEST.sha256: hashes de todos os arquivos de evidênciaO runner usa uma rede Podman interna e publica o WordPress apenas em 127.0.0.1. O cliente não aceita endereço de destino. A origem não aceita entrada de comandos. O comando fixo lê apenas a flag sintética.
O runner remove o valor da flag do ambiente do Apache antes de o Apache iniciar. Ele também cria credenciais novas e remove apenas recursos da execução atual.
A divisão da resposta e o histórico de processos são entradas do exploit. Se uma execução falhar, repita o perfil inalterado com uma nova tag. Não ajuste endereços nem bytes de payload após uma execução falha.
Este pacote não estabelece portabilidade entre digests de imagem, builds do PHP, arquiteturas, alocadores ou cronogramas de resposta. Um alvo diferente pode interromper a execução ou causar apenas um crash.
O upstream corrigiu os branches mantidos do Brotli em 1.0.10, 1.1.1 e 1.2.1. Fornecedores de distribuição podem fazer backport da correção. Use o aviso do fornecedor e o changelog do pacote, não apenas a versão exibida.