joaovicdev
exploitexploitCVE-2026-87902

CVE-2026-87902: WordPress double-encoded path traversal — LFI to RCECVE-2026-87902: path traversal com dupla codificação no WordPress — de LFI a RCE

A dockerized lab for CVE-2026-87902 — WordPress Core resolves the public pagename parameter, re-decodes it after validation, and lets a double-encoded ../ traverse the filesystem, chaining into RCE through pearcmd.php.Um lab dockerizado da CVE-2026-87902 — o WordPress Core resolve o parâmetro público pagename, o decodifica de novo depois de validar, e deixa um ../ com dupla codificação percorrer o sistema de arquivos, encadeando até RCE via pearcmd.php.

Proof of concept on GitHubProva de conceito no GitHub

Authorization: this is a self-contained lab built for education and security research. The vulnerable WordPress stack runs entirely inside Docker on your own machine. Do not point the exploit at sites you don't own or aren't explicitly authorized to test — misuse can be a crime.

Summary

CVE-2026-87902 is a CVSS 4.0 9.2 (Critical) path traversal bug (CWE-22) in WordPress Core 4.7.0 – 7.1.1. The page-template resolution logic validates the public pagename query parameter against traversal sequences, then URL-decodes it a second time without re-validating — so a double-encoded ../ slips past the check and reaches the template include as a real directory traversal.

Unauthenticated, it yields local file inclusion. Where a handful of common conditions line up, the same primitive chains into remote code execution via PEAR's pearcmd.php. This repository packages a vulnerable WordPress stack, the lab environment, and a Python exploit that drives both the LFI and the RCE path.

How the vulnerability works

WordPress resolves a requested page by feeding the pagename query variable into its template lookup. The guard rejects raw ../ sequences, but the value is then decoded again before it's used to build the include path — and that second decode is never re-checked.

Double URL encoding walks straight through the gap:

one ../ segment, twice-encoded
%252e%252e%252f      ← what the attacker sends
%2e%2e%2f            ← after the 1st decode: passes the traversal check
../                  ← after the 2nd decode: used in the include path

The validator only ever inspects the first form; the include only ever sees the last. Chain the segment and you climb out of the template directory to anywhere the PHP process can read.

Affected versions

WordPress CoreStatus
4.7.0 – 7.1.1Vulnerable
7.1.2Fixed
4.7.37 (backport)Fixed

Impact

At minimum, an unauthenticated attacker can include arbitrary local files — enough to leak source and secrets (for example wp-config.php, which holds the database credentials and auth keys).

The escalation to RCE needs three preconditions to hold at once. All three are present in the lab:

PreconditionWhy it's needed for RCE
An active theme with a page- prefixed directoryGives the resolver a path the traversal can pivot from
PHP register_argc_argv enabledLets query-string tokens reach pearcmd.php as CLI argv
pearcmd.php present on diskThe gadget: turns those argv tokens into a written file

When they do, the traversal reaches pearcmd.php and abuses its config-create behaviour to write attacker-controlled PHP to a known path, which is then executed on a follow-up request.

Proof of concept

LFI — read a local file by traversing out of the template directory with the double-encoded segment:

LFI — arbitrary local file read
GET /?pagename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd HTTP/1.1
Host: localhost:8091

Swap the tail for wp-config.php to pull WordPress's database credentials and secret keys.

RCE — the pearcmd.php chain. With the three preconditions above satisfied, the traversal targets PEAR's command handler and, via register_argc_argv, feeds it the tokens that make config-create drop a PHP stager to disk; a second request executes it. The exploit script wires the full chain end to end — see exploit/exploit.py in the repo for the exact request sequence and payload construction.

Run it

Prerequisites: Docker, Docker Compose, and Python 3.

1. Bring up the vulnerable stack:

terminal 1
docker compose up --build

2. Install the exploit's dependencies:

terminal 2 — in exploit/
pip install -r requirements.txt

3. Run the exploit — it targets http://localhost:8091 by default:

terminal 2
python3 exploit.py

Pick a mode to run just the LFI, or drive the RCE with a command of your choice:

python3 exploit.py --mode lfi
python3 exploit.py --mode rce --cmd 'id'

Mitigation

  • Update to WordPress 7.1.2 or later (or the 4.7.37 backport).
  • Disable register_argc_argv in your PHP configuration — it severs the pearcmd RCE chain even on an unpatched core.
  • Remove or restrict access to pearcmd.php on production hosts.

References

Autorização: este é um lab autocontido, feito para fins educacionais e pesquisa de segurança. A stack WordPress vulnerável roda inteiramente dentro do Docker, na sua própria máquina. Não aponte o exploit para sites que você não possui ou que não está explicitamente autorizado a testar — uso indevido pode ser crime.

Resumo

A CVE-2026-87902 é uma falha de path traversal (CWE-22) CVSS 4.0 9.2 (Crítica) no WordPress Core 4.7.0 – 7.1.1. A lógica de resolução de templates de página valida o parâmetro público pagename contra sequências de traversal e, em seguida, decodifica a URL uma segunda vez sem revalidar — de modo que um ../ com dupla codificação passa pela verificação e chega ao include do template como um traversal de diretório de verdade.

Sem autenticação, isso resulta em local file inclusion. Quando algumas condições comuns se alinham, a mesma primitiva encadeia até execução remota de código por meio do pearcmd.php do PEAR. Este repositório empacota uma stack WordPress vulnerável, o ambiente de lab e um exploit em Python que conduz tanto o caminho de LFI quanto o de RCE.

Como a vulnerabilidade funciona

O WordPress resolve uma página solicitada passando a variável de query pagename para a busca de template. A proteção rejeita sequências ../ cruas, mas o valor é então decodificado de novo antes de ser usado para montar o caminho do include — e essa segunda decodificação nunca é reverificada.

A dupla codificação de URL atravessa direto pela brecha:

um segmento ../, codificado duas vezes
%252e%252e%252f      ← o que o atacante envia
%2e%2e%2f            ← após a 1ª decodificação: passa na verificação de traversal
../                  ← após a 2ª decodificação: usado no caminho do include

O validador só inspeciona a primeira forma; o include só enxerga a última. Encadeie o segmento e você sobe para fora do diretório de templates até qualquer lugar que o processo PHP consiga ler.

Versões afetadas

WordPress CoreStatus
4.7.0 – 7.1.1Vulnerável
7.1.2Corrigido
4.7.37 (backport)Corrigido

Impacto

No mínimo, um atacante não autenticado consegue incluir arquivos locais arbitrários — o suficiente para vazar código-fonte e segredos (por exemplo o wp-config.php, que guarda as credenciais do banco e as chaves de autenticação).

A escalada para RCE exige três pré-condições simultâneas. Todas as três estão presentes no lab:

Pré-condiçãoPor que é necessária para o RCE
Um tema ativo com um diretório prefixado por page-Dá ao resolver um caminho a partir do qual o traversal pivota
PHP register_argc_argv habilitadoFaz tokens da query string chegarem ao pearcmd.php como argv
pearcmd.php presente em discoO gadget: transforma esses tokens argv em um arquivo escrito

Quando isso acontece, o traversal alcança o pearcmd.php e abusa do comportamento config-create para escrever PHP controlado pelo atacante em um caminho conhecido, que é então executado em uma requisição seguinte.

Prova de conceito

LFI — ler um arquivo local percorrendo para fora do diretório de templates com o segmento de dupla codificação:

LFI — leitura de arquivo local arbitrário
GET /?pagename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd HTTP/1.1
Host: localhost:8091

Troque o final por wp-config.php para extrair as credenciais do banco e as chaves secretas do WordPress.

RCE — a cadeia do pearcmd.php. Com as três pré-condições acima satisfeitas, o traversal mira o handler de comandos do PEAR e, via register_argc_argv, alimenta os tokens que fazem o config-create gravar um stager PHP em disco; uma segunda requisição o executa. O script do exploit conecta a cadeia inteira ponta a ponta — veja exploit/exploit.py no repositório para a sequência exata de requisições e a construção do payload.

Rodando o lab

Pré-requisitos: Docker, Docker Compose e Python 3.

1. Suba a stack vulnerável:

terminal 1
docker compose up --build

2. Instale as dependências do exploit:

terminal 2 — em exploit/
pip install -r requirements.txt

3. Rode o exploit — ele mira http://localhost:8091 por padrão:

terminal 2
python3 exploit.py

Escolha um modo para rodar só o LFI, ou conduza o RCE com um comando à sua escolha:

python3 exploit.py --mode lfi
python3 exploit.py --mode rce --cmd 'id'

Mitigação

  • Atualize para o WordPress 7.1.2 ou posterior (ou o backport 4.7.37).
  • Desabilite o register_argc_argv na sua configuração do PHP — isso corta a cadeia de RCE do pearcmd mesmo em um core sem patch.
  • Remova ou restrinja o acesso ao pearcmd.php em hosts de produção.

Referências