
PoC de RCE sin autenticación para el núcleo de WordPress — encadena CVE-2026-63030 (route-confusion desync en REST /batch/v1) con CVE-2026-60137 (SQLi en author__not_in) para obtener un shell sin autenticación. Solo pruebas autorizadas.
PoC de RCE no autenticado en el núcleo de WordPress que encadena CVE-2026-63030 + CVE-2026-60137. Solo pruebas autorizadas.
wp2shell.py es una prueba de concepto de un solo archivo que usa únicamente la biblioteca estándar y encadena
dos vulnerabilidades del núcleo de WordPress para lograr ejecución remota de código no autenticada:
| CVE | Componente | Fallo |
|---|---|---|
| CVE-2026-63030 | Manejador REST /batch/v1 | desync por confusión de rutas: el manejador de lotes pierde la sincronía entre su tabla de manejadores y su tabla de validación cuando una ruta de sub-solicitud no se puede analizar, de modo que una sub-solicitud se despacha a través de un manejador para el que nunca fue validada. |
| CVE-2026-60137 | WP_Query | inyección SQL escalar: el valor de author__not_in se inserta directamente en el SQL, por lo que una cadena que llegue hasta él es inyectable. |
Ninguna de las dos es RCE por sí sola. El desync es el medio de entrega (introduce una cadena del atacante en una consulta a la que nunca debería llegar); la inyección es la primitiva. Juntas le dan a un atacante sin autenticación un oráculo completo de lectura/escritura SQL y, a partir de ahí, una shell funcional.
⚠️ Solo pruebas autorizadas. Esto explota instalaciones de WordPress en producción. Úsalo solo contra sistemas que poseas o para los que tengas permiso explícito por escrito.
Vulnerables: 6.8.0–6.8.5, 6.9.0–6.9.4, 7.0.0–7.0.1 Corregidas: 6.8.6, 6.9.5, 7.0.2 (y posteriores) Reportadas por Adam Kues (Assetnote / Searchlight Cyber); la SQLi también acreditada a TF1T, dtro, haongo.
Solo 6.9.0–7.0.1 son capaces de RCE. En la línea 6.8.x el desync aún se dispara, pero la
rama que desalinearía el manejador choca con un fatal WP_Error::get_method() en su lugar —
así que 6.8.x es una denegación de servicio / caída, no una shell. La herramienta lo detecta
y te indica qué primitiva está disponible.
El exploit se construye en capas; cada una es un puente hacia la siguiente.
Desync (CVE-2026-63030). Una solicitud por lotes anida un segundo lote. Una sub-solicitud
lleva una ruta deliberadamente malformada (http://:, que wp_parse_url() rechaza).
En 6.9.0+ el fallo de análisis hace que las tablas de manejadores y de validación se desvíen una entrada,
de modo que la siguiente sub-solicitud se ejecuta bajo un manejador contra el que nunca fue validada: la
colección pública de posts.
Inyección (CVE-2026-60137). Esa llamada mal despachada llega a WP_Query con un
author__not_in controlado por el atacante. Un UNION ALL SELECT ahí no lee filas —
las fabrica, lo que nos permite forjar posts arbitrarios en el resultado de la consulta.
Puente SQL → escritura. Los posts fabricados llevan marcado [embed]. WordPress
los resuelve fielmente y los almacena en caché como filas reales de oembed_cache post-meta — convirtiendo
una inyección de solo lectura en una primitiva de escritura arbitraria.
Escalada de privilegios. Usando ese puente de escritura forjamos (a) un changeset
del personalizador con autoría del administrador real y (b) un post request autorreferencial. Juntos
fuerzan a WordPress a re-ejecutar su propio pipeline de solicitudes como ese administrador — una ventana
lo bastante larga para que una llamada encolada a POST /wp/v2/users cree un nuevo administrador.
Shell. Inicia sesión como el nuevo administrador, sube un plugin, ejecuta el comando. La cuenta, su meta, las filas oembed y la webshell se limpian después — no queda ninguna huella persistente.
Para el lado de lectura (--dump, resolución de prefijo, IDs en caché) la herramienta selecciona automáticamente el
oráculo más rápido que funcione y se detiene en el primer acierto:
union — UNION en banda: el post_title de la fila forjada lleva |||HEX(value)|||,
que se refleja textualmente en el cuerpo de la respuesta de /wp/v2/posts. Lee un valor completo
(de cualquier longitud) en una sola solicitud. Si UNION se refleja, los oráculos booleanos nunca se prueban.
bool — oráculo booleano X-WP-Total: el encabezado X-WP-Total de la sub-respuesta confundida
es count>0 para verdadero / 0 para falso. Un bit por solicitud (bisección).
time — SLEEP() basado en tiempo: el recurso de respaldo cuando el cuerpo se elimina y la
respuesta es un 500 (p. ej. un fatal posterior al despacho de un mu-plugin de caché). Funciona incluso en
objetivos que fallan en cada llamada REST.
--dump usa GROUP_CONCAT para extraer todo el conjunto de credenciales de una sola vez cuando UNION está
disponible.
Ninguna. Solo biblioteca estándar de Python 3.7+ — sin pip install.
chmod +x wp2shell.py
# 1. Is it vulnerable? (no injection, no writes, no account created)
./wp2shell.py http://target --check
# 2. Read-only credential dump — FIRST administrator + option secrets
./wp2shell.py http://target --dump
# …every administrator
./wp2shell.py http://target --dump all
# 3. Full RCE — create a temp admin, run a command, clean up
./wp2shell.py http://target --exec "id; uname -a"
./wp2shell.py http://target --user-list # full user table (logins/emails/roles)
./wp2shell.py http://target --content # every post type at status=any (private/draft)
./wp2shell.py http://target --content full # …including each item's raw body
./wp2shell.py -f targets.txt --check --target-threads 8
| Flag | Propósito |
|---|---|
url / -f FILE | objetivo único, o un archivo de objetivos (una URL por línea) |
--check | solo verificación de vulnerabilidad — sin inyección, escrituras o cuentas |
--dump [all] | volcar el hash + secretos del primer admin; all = todos los admins |
--exec CMD | crear admin temporal → ejecutar CMD → limpiar |
--user-list | lectura en banda elevada de la tabla de usuarios completa (sin login, evita 2FA) |
--content [full] | lectura elevada de todos los tipos de post en status=any; full añade los cuerpos |
--oracle {auto,union,bool,time} | forzar un oráculo de extracción (por defecto auto) |
--prefix PFX | forzar el prefijo de tabla en lugar de resolverlo automáticamente |
--impersonate UID | suplantar este id de usuario en lugar del primer admin |
--threads N | sondas de extracción concurrentes dentro de un objetivo (por defecto 5) |
--target-threads N | escanear N objetivos de -f concurrentemente (por defecto 1) |
--proxy URL | enrutar a través de un proxy (p. ej. Burp http://127.0.0.1:8080) |
--timeout SEC | tiempo de espera por solicitud (por defecto 30) |
--verbose / -v | volcar sub-respuestas de lote crudas (depuración) |