flawopen.com/Teardowns/cve-2026-87902-wordpress-page-template-path-traversal
CVE-2026-87902: Traversal de rutas en plantillas de WordPress
Un nombre de página decodificado sin validar entró en la resolución de plantillas, permitiendo incluir PHP local sin autenticación y RCE condicional bajo requisitos concretos de tema y entorno.[1]
Imagina a una bibliotecaria que comprueba cada petición. WordPress revisaba una ruta hacia las plantillas, pero no la creada desde el nombre decodificado; ciertos marcadores podían sacarla de las estanterías del tema. El arreglo valida el nombre y mantiene la plantilla dentro de un directorio permitido.[1], [2], [3]
Conceptos Clave y Términos
Resolución de plantillas- WordPress ordena candidatos PHP y busca el primer archivo coincidente en el tema activo.[1], [2]
Traversal de rutas- Componentes como
..pueden salir del directorio previsto si la entrada no se valida.[1] Inclusión local de PHP- Carga de un archivo PHP ya presente en el servidor; la RCE requiere otras condiciones.[1]
`validate_file()`- Ayudante de WordPress que rechaza componentes de ruta no permitidos antes de usarlos.[3]
Mapeos de marcos
- Referencia CWE para la debilidad descrita en esta guía: CWE-98
- CISA KEV — añadido el 2026-09-25 — CVE-2026-87902
Análisis de Causa Raíz
En get_page_template(), WordPress decodificaba pagename y añadía page-{name}.php sin validate_file(), aunque la rama contigua sí validaba. Así, la ruta podía salir del tema. Se requiere un directorio superior page-* y un PHP local legible; la RCE exige condiciones adicionales de PHP/servidor. La versión 7.1.2 valida el valor decodificado y limita la ruta resuelta en locate_template(). El aviso de WordPress clasifica esta debilidad como CWE-98 (control inadecuado del nombre de archivo usado en include/require de PHP) y acredita a Robert Ressl el descubrimiento y la divulgación responsable.[1], [2], [3], [7]
Alcance de CVE-2026-87902: exposición y RCE condicional
| Condición del despliegue | Qué confirman las fuentes primarias | Acción del operador |
|---|---|---|
| Rama afectada de WordPress Core | El GHSA enumera las versiones afectadas por rama, desde 4.7.0–4.7.36 hasta 7.1.0–7.1.1. Las correcciones correspondientes van de 4.7.37 (el backport más antiguo) a 7.1.2 (rama actual), con una versión corregida específica para cada rama enumerada. | Comprueba la versión instalada y aplica la corrección de su rama. Actualiza las versiones anteriores a 4.7 a una versión con soporte. |
| Estructura del tema activo | El tema padre o hijo activo debe contener un directorio de nivel superior cuyo nombre empiece por page- (por ejemplo, page-templates). | Revisa los directorios de ambos temas activos. Vuelve a comprobar esta condición si cambia el tema activo. |
| Archivo PHP local legible | Debe existir un archivo .php elegido fuera de los directorios de los temas activos y ser legible por la cuenta del servidor web. | Revisa qué archivos PHP locales puede leer el servidor web; el rango de versiones del CVE no demuestra por sí solo que se haya incluido un archivo. |
| Entorno para RCE condicional | El aviso describe RCE solo con condiciones adicionales de PHP/servidor. Sus ejemplos incluyen pearcmd.php con register_argc_argv=On, la imagen oficial de PHP para Docker y configuraciones predeterminadas de cPanel con PHP anterior a 8.5. | Evalúa el runtime y la configuración PHP reales. Una versión vulnerable de WordPress no demuestra por sí sola RCE ni compromiso. |
Flujo de Ataque Paso a Paso
La entrada llega a la selección
Una petición no autenticada aporta el nombre usado para crear candidatos; no necesita cuenta ni clic.[1]
La búsqueda sale de la raíz
Con un directorio page-* y un PHP local legible, el tema puede resolver un archivo fuera de su directorio.[1]
Impacto condicional
La RCE depende de otros requisitos PHP/servidor; la versión por sí sola no demuestra explotabilidad ni intrusión.[1]
Código Fuente: Vulnerable vs. Seguro
Este fragmento compara las etiquetas oficiales 7.1.1 y 7.1.2. El parche valida el valor después de decodificarlo; la misma versión también limita las rutas resueltas en locate_template(). Es código upstream, no un parche independiente completo ni una demostración de explotación.[2], [3]
Fragmento de origen: wp-includes/template.php — get_page_template()
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}
if ( $pagename ) {
$pagename_decoded = urldecode( $pagename );
if ( $pagename_decoded !== $pagename && 0 === validate_file( $pagename_decoded ) ) {
$templates[] = "page-{$pagename_decoded}.php";
}
}
Lista de Verificación de Seguridad para Ingeniería
- ✓Actualiza a la versión corregida de la rama y verifica el código que realmente se ejecuta.[1], [4]
- ✓Inventaría WordPress expuesto a Internet e identifica versiones sin soporte anteriores a 4.7.[1], [4]
- ✓Si hubo exposición antes del parche, revisa registros y archivos PHP o cuentas inesperados; una señal inicia la investigación, no prueba por sí sola una intrusión.
- ✓En resolutores propios, valida los componentes tras decodificarlos y limita la ruta real canónica a directorios permitidos.[1], [3]
- ✓Mantén Core y los temas en versiones compatibles; cambiar el tema puede modificar las condiciones de exposición.[1], [4]