
Análisis e implementación de extremo a extremo de la vulnerabilidad RCE parcheada de wordpress - CVE-2026-60137 y CVE-2026-63030

Todos los PoC weaponizan los mismos dos bugs: la confusión de rutas del batch REST (CVE-2026-63030) y la inyección SQL author__not_in (CVE-2026-60137), con la misma forma de batch doblemente anidado. Lo que difiere entre ellos es la ruta de RCE elegida, las precondiciones del entorno y los valores de seguridad por defecto. Este documento establece, de forma concreta, dónde se sitúa la implementación de este repositorio en ese panorama.
check no envía payload SQL a menos que se le pida; todo el tráfico puede llevar una etiqueta de atribución; todo lo que el comando shell escribe en el objetivo se elimina automáticamente después. Ver §3.[1] Solo timing/a ciegas como canal de lectura; la primitiva UNION de post falso existe dentro del puente pero no se expone como oráculo de extracción.
[2] El sondeo de disponibilidad ingenuo (0) UNION SELECT …) se descarta silenciosamente durante la hidratación de la caché de objetos, available() devuelve false y todo el puente pre-autenticación se aborta - ver §1.
[3] El propio README del proyecto enumera "sin caché de objetos persistente (Redis/Memcached)" entre las Precondiciones.
[4] Variante pública disponible en Sploitus (enlace abajo): lectura a ciegas más un dropper INTO OUTFILE como paso de RCE, usando un carrier de categorías per_page=-1.
[5] La ruta RCE por OUTFILE no depende del renderizado de post falso, por lo que las cachés de objetos no la bloquean - lo hacen el privilegio FILE de MySQL y un directorio compartido escribible. El hosting gestionado casi nunca concede FILE al usuario de la BD de WordPress, y secure_file_priv suele estar establecido.
La primitiva UNION de post falso depende de cómo WP_Query devuelve las filas:
wp_posts completas; una fila inyectada por UNION se convierte directamente en un WP_Post. La falsificación se renderiza.En hosts con caché de objetos persistente, un conjunto de resultados base poblado empuja a WP_Query al modo dividido. El sondeo estándar usado por los PoC públicos -
0) UNION SELECT <forged row> -- -
post_author NOT IN (0) coincide con todas las filas), por lo que detrás de una caché de objetos la fila falsificada se evapora: el sondeo de disponibilidad da falso negativo, available() devuelve false y todo el puente pre-autenticación se reporta "muerto" en un host que de hecho es totalmente explotable. El unificador público documenta el mismo límite enumerando "sin caché de objetos persistente" como precondición estricta.Este repo vacía el conjunto base en su lugar:
1) AND 1=0 UNION ALL SELECT <forged row> -- -
Con cero filas base, la fila falsificada es la única fila; la consulta permanece en modo fila completa; nunca se ejecuta una búsqueda de hidratación. Una palabra clave inyectada (AND 1=0) es toda la diferencia entre "canal UNION muerto" y "RCE pre-autenticación completo" en hosts con caché de objetos - que son la mayoría de los entornos de producción de WordPress gestionado. El diagnóstico, la matriz de sondeos (per_page × forma de inyección).
Nota de alcance: el canal de lectura a ciegas/timing no es sensible a la caché de objetos (contar filas en SQL no implica hidratación de post falso), por lo que la lectura a ciegas de cada PoC funciona en todas partes. Lo que la caché de objetos mata en los otros PoC es específicamente la parte dependiente de UNION: la extracción en banda y el puente SQLi-a-admin.
Una segunda lección relacionada documentada en el caso de estudio: cuando ambos canales funcionan, trate la lectura UNION en banda como autoritativa - el oráculo de timing en producción produjo bit flips bajo jitter en un valor que la lectura en banda resolvió sin ambigüedad.
Existen tres rutas de RCE pre-autenticación en los PoC públicos:
Este repo implementa el puente: no necesita privilegios de base de datos más allá de los que WordPress ya tiene, funciona cuando los niveles de BD y web no comparten nada, y no deja ningún archivo del que dependa la ruta del privilegio FILE. El trade-off es la complejidad - el puente es un grafo de posts envenenados de siete filas - que es exactamente donde el falso negativo de caché de objetos de la §1 solía ocultar la ruta.
Construido para ejecutarse contra sistemas en producción bajo autorización, no solo laboratorios:
check no es destructivo por defecto - huella pasiva más un batch de marcadores benigno; no se envía payload SQL a menos que se indique --confirm-sqli. Después de parchear, el trío de marcadores que desaparece sirve además como validación de la corrección.--user-agent en cada comando para que todo el tráfico del exploit sea identificable en los logs (una regla práctica de engagement que las herramientas públicas no aplican por defecto).author_exclude → author__not_in, la primitiva UNION de WP_Post falso y el concepto del puente customizer son todas técnicas públicas (linaje reconocido abajo).INTO OUTFILE), disponible en Sploitus -
https://sploitus.com/exploit?id=7CD079AD-E27B-5C54-A696-60635BFDB241| Capacidad | Este repo | Icex0/wp2shell-poc | sergiointel/wp2shell-poc | 0xsha/wp2shell | Variante OUTFILE [4] |
|---|
| Lectura SQLi a ciegas/timing pre-autenticación | sí | sí | sí (timing) | sí | sí |
| Lectura UNION en banda (1 petición/valor) | sí | sí | - [1] | - [1] | - |
| Lectura basada en errores (EXTRACTVALUE) | sí | sí | - | - | - |
| El canal UNION sobrevive a la caché de objetos persistente | sí (base eliminada) | no - los sondeos dan falsos negativos [2] | no documentado | no - precondición documentada [3] | n/a [5] |
| RCE pre-autenticación sin crackeo | sí (puente SQLi-a-admin) | sí (mismo puente) | sí (origen del puente) | sí (mismo puente) | sí, vía INTO OUTFILE [5] |
| Precondiciones adicionales para RCE | ninguna más allá de la instalación por defecto | ninguna (en hosts sin caché de objetos) | ninguna (igual) | ninguna (igual) | privilegio FILE de MySQL + ruta escribible por la web compartida con mysqld |
| Verificación no destructiva / validación de parche | sí (trío de marcadores; sin payload por defecto) | sí | no | sí (block_cannot_read) | sí (batch de marcadores) |
| Etiquetado de atribución/User-Agent | sí, en todos los comandos | no | no | flag de transporte | no |
| Limpieza automática (webshell + admin generado) | sí | sí | no documentado | solo webshell con token | dropper eliminado [5] |
| Guía de detección para el blue team | sí, a partir de una ejecución en producción | no | no | matriz de laboratorio en su lugar | notas de mitigación |
| Dependencias | solo stdlib | solo stdlib | archivo único | solo stdlib, archivo único | paquete Python ≥3.10 |
| Ruta | Usada por | Precondiciones adicionales |
|---|
Puente SQLi-a-admin (falsear filas oEmbed/changeset/nav → POST /wp/v2/users → login → subida de plugin) | este repo, sergiointel (origen), Icex0, 0xsha | ninguna más allá de una instalación por defecto |
Dropper INTO OUTFILE (escribir un archivo PHP vía SQLi, obtenerlo para un shell) | variante OUTFILE [4] | privilegio FILE de MySQL, secure_file_priv que lo permita, y un directorio escribible por mysqld y servido por el servidor web |
Recuperación de hash → crackeo → login (volcar user_pass, crackear offline y luego subir el plugin) | todos (como respaldo) | el hash bcrypt debe poder crackearse realmente ($wp$2y$, hashcat -m 35500) - lento, a menudo nunca |