Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
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
wp2shell — PoC para CVE-2026-63030 + CVE-2026-60137, también conocido como WP2Shell | Kitploit
Herramientas/GitHubGitHub/crypto-cat/wp2shell
Escáneres de VulnerabilidadesAnálisis de CódigoExplotaciónSeguridad WebAprendizaje y Educación
GitHubcrypto-cat/wp2shell

wp2shell

PoC para CVE-2026-63030 + CVE-2026-60137, también conocido como WP2Shell

Ver Repositorio
3hace 1 mesAún no revisado

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

wp2shell

Ejecución remota de código pre-autenticación para WordPress 6.9.0–6.9.4 y 7.0.0–7.0.1.

Encadena CVE-2026-63030 (confusión de rutas batch SQLi) con CVE-2026-60137 (re-entrada de changeset del personalizador) para lograr la creación de un administrador sin autenticación y la ejecución de comandos del sistema operativo. No se requiere cracking de contraseñas.

demo de wp2shell

Créditos a hashkitten por el descubrimiento; lee el análisis técnico completo de SLCyber aquí.

La vulnerabilidad

El procesador de batch de la API REST de WordPress (serve_batch_request_v1) tiene un error de indexación off-by-one: cuando wp_parse_url() falla en la ruta de una sub-petición, el WP_Error resultante se añade a $validation[] pero no a $matches[]. Esto desincroniza los dos arrays — cada petición posterior se despacha bajo el manejador incorrecto.

Anidando un batch cuidadosamente estructurado dentro de otro batch, un atacante puede:

  1. Enrutar una petición validada por el esquema de un endpoint a través del callback de un endpoint completamente distinto
  2. Inyectar SQL sin sanitizar a través de author__not_in (la conversión string→array omite absint())
  3. Usar UNION SELECT para envenenar la caché de objetos de WordPress con objetos de post falsos
  4. Disparar una auto-publicación de changeset que eleva privilegios y luego re-entrar en la API REST con contexto de administrador

Una vez completada la preparación (descubriendo el prefijo de tabla y el ID de administrador), el payload de escalada se dispara en una sola petición HTTP — el envenenamiento de caché, la escalada de privilegios y la creación de usuarios ocurren todos en el servidor en un único round-trip.

Cómo funciona la cadena

root@kitploit:~
HTTP POST /batch/v1
    │
    ▼
┌─ Outer Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                  → parse error, not added to $matches      │
│  [1] POST /wp/v2/posts    → $matches[0] (posts handler)             │
│  [2] POST /batch/v1       → $matches[1] (batch handler)             │
│                                                                     │
│  Desync: request[1] dispatched via $matches[1]                      │
│          POST /wp/v2/posts body interpreted as batch → inner fires  │
│                                                                     │
└──────────────────────────────────────┬──────────────────────────────┘
                                       │
    ┌──────────────────────────────────┘
    ▼
┌─ Inner Batch ───────────────────────────────────────────────────────┐
│                                                                     │
│  [0] ///                            → parse error (desync)          │
│  [1] GET  /wp/v2/widgets?UNION...   → dispatched by posts handler   │
│          ▲ WP_Query fires UNION, poisons object cache               │
│          ▲ the_content renders [embed] → oEmbed → hierarchy Loop 1  │
│              → changeset published → admin context set              │
│              → nav_menu_item UPDATE → hierarchy Loop 2              │
│                  → parse_request → REST re-entry ─────────────┐     │
│                                                               │     │
│  [2] GET  /wp/v2/posts              (categories handler)      │     │
│  [3] GET  /wp/v2/categories         (users handler)           │     │
│  [4] POST /wp/v2/users  {body}  ◄── re-entry with admin ──────┘     │
│          ▲ desync aligns this with users handler                    │
│          ▲ admin context → user created → die()                     │
│  [5] POST /wp/v2/users  {}          (desync spacer)                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Envenenamiento de caché (7 posts falsos vía UNION):

  • Un post desencadenante con un shortcode [embed] en su contenido
  • Un post de changeset (customize_changeset, estado future, fecha en el pasado)
  • Un compañero de bucle externo (parent=changeset, creando el Bucle 1)
  • Un objetivo oEmbed (ID anti-recursión dinámico, parent=changeset, contenido vacío)
  • Un post de elemento de menú de navegación (envenenado como post_type=nav_menu_item para la comprobación is_nav_menu_item)
  • Un post de re-entrada (post_type=request, post_status=parse, parent=inner)
  • Un compañero de bucle interno (parent=re-entry, creando el Bucle 2)

Flujo de ejecución:

  1. UNION envenena la caché de objetos con los 7 posts falsos
  2. El manejador de posts renderiza el contenido del post desencadenante → el shortcode [embed] se dispara
  3. La búsqueda en caché de oEmbed encuentra un post de respaldo con contenido vacío → pasa a wp_update_post
  4. wp_update_post lee el changeset en caché (parent=outer) → la comprobación de jerarquía detecta el Bucle 1
  5. La corrección escribe el changeset en la BD con estado future → se convierte automáticamente a publish
  6. _wp_customize_publish_changeset se dispara → wp_set_current_user(admin_id) → contexto de administrador activo
  7. El changeset procesa nav_menu_item[real_id] — la caché dice type=nav_menu_item → ruta UPDATE
  8. object_id se resuelve a un post en caché con post_parent=re-entry → wp_update_post sobre el post real

Una variable de sesión MySQL anti-recursión (@_wp2s) garantiza que la cadena se dispare exactamente una vez y no entre en bucle.

Características

  • Tres modos de extracción con detección automática: UNION (1 petición/valor), basado en errores vía EXTRACTVALUE (~30 caracteres/petición), búsqueda binaria boolean-blind (~7 peticiones/carácter)
  • RCE completo pre-autenticación — sin credenciales, sin cracking, la escalada se dispara en un solo round-trip
  • Auto-descubrimiento — prefijo de tabla vía INFORMATION_SCHEMA, ID de usuario administrador vía meta de capacidades
  • Post-explotación — webshell de plugin con autenticación por token, shell interactiva con seguimiento de CWD, lectura/escritura de archivos
  • Modo limpieza — --cleanup elimina el usuario creado y retira la webshell al salir
  • Cero dependencias — solo stdlib, un único archivo, funciona en Python 3.8+

Instalación

root@kitploit:~
git clone https://github.com/Crypto-Cat/wp2shell.git
cd wp2shell
chmod +x wp2shell.py

Sin pip install, sin virtualenv. Es un solo archivo.

Uso

Comprobar si un objetivo es vulnerable

root@kitploit:~
# Prueba pasiva de oráculo booleano
python3 wp2shell.py check http://target.com

# Confirmar también con timing y UNION
python3 wp2shell.py check http://target.com --confirm-timing --confirm-union

Extraer datos

root@kitploit:~
# Selecciona automáticamente la técnica más rápida (UNION > error > blind)
python3 wp2shell.py read http://target.com --preset users
python3 wp2shell.py read http://target.com --preset secrets
python3 wp2shell.py read http://target.com --query "SELECT @@version"

# Forzar una técnica específica
python3 wp2shell.py read http://target.com --technique blind --preset users

# Auto-descubrir el prefijo de tabla
python3 wp2shell.py read http://target.com --auto-prefix --preset users

Explotación completa

root@kitploit:~
# Explotar y entrar en una shell interactiva
python3 wp2shell.py exploit http://target.com -i

# Explotar, ejecutar un comando y limpiar
python3 wp2shell.py exploit http://target.com -c "cat /etc/passwd" --cleanup

# Omitir el auto-descubrimiento si conoces el prefijo
python3 wp2shell.py exploit http://target.com --prefix wp_ --no-discover -i

# A través de un proxy (Burp, mitmproxy, etc.)
python3 wp2shell.py exploit http://target.com --proxy http://127.0.0.1:8080 -i

Shell autenticada (con credenciales existentes)

root@kitploit:~
python3 wp2shell.py shell http://target.com --user admin --password 'P@ssw0rd' -i

Requisitos para el RCE completo

Los comandos check y read funcionan en cualquier objetivo afectado. La cadena de exploit tiene tres requisitos adicionales:

Si el objetivo usa Redis o Memcached como caché de objetos, split_the_query se fuerza activado independientemente de per_page, y las filas UNION se descartan durante la recuperación solo de IDs. El comando read sigue funcionando (la extracción blind no necesita que UNION sobreviva en la caché), pero exploit fallará.

Versiones afectadas

RamaVulnerableCorregida
6.9.x6.9.0 – 6.9.46.9.5
7.0.x7.0.0 – 7.0.17.0.2

El parche añade $matches[] = $single_request; para los casos de error (corrigiendo el off-by-one) y una protección contra la re-entrada en serve_request().

Arquitectura

root@kitploit:~
wp2shell.py (single file, ~1650 lines)
├── Client          HTTP transport with batch URL negotiation
├── Desync          Nested batch payload construction
├── BlindExtractor  Boolean binary search (universal)
├── UnionExtractor  In-band via forged post_title (fastest)
├── ErrorExtractor  EXTRACTVALUE-based (intermediate)
├── PoisonGraph     Hierarchy loop structure for cache poisoning
├── Exploiter       Chain orchestration (seed → extract → escalate)
└── AdminSession    Authenticated session, webshell, cleanup

Detalles técnicos

¿Por qué /wp/v2/widgets como ruta de origen?

El controlador de Widgets no registra per_page, orderby ni author_exclude en el esquema de su endpoint. Estos parámetros pasan la validación sin modificarse (los parámetros desconocidos son ignorados por el validador de esquema). Cuando el desync despacha esta petición a través del controlador de Posts, esos valores crudos fluyen directamente hacia WP_Query.

¿Por qué per_page=500?

class-wp-query.php:3375 — split_the_query requiere !empty($limits) && posts_per_page < 500. Con per_page=500, la condición 500 < 500 es falsa, por lo que split_the_query queda deshabilitado. La consulta completa (incluyendo UNION) se ejecuta como una sola sentencia, y todas las filas inyectadas sobreviven en el conjunto de resultados y en la caché.

¿Por qué nav_menu_item[real_id] (ID positivo)?

Usar un ID de post positivo entra en la ruta UPDATE en nav-menu.php:614, que llama a wp_update_post con un $post_id distinto de cero. Esto es crítico porque wp_check_post_hierarchy_for_loops en post.php:8070 retorna antes de tiempo cuando $post_id = 0 (posts nuevos). La caché se envenena con post_type=nav_menu_item para ese ID, de modo que is_nav_menu_item() supera la comprobación de tipo en nav-menu.php:426. La ruta UPDATE dispara entonces la comprobación de jerarquía que detecta el Bucle 2.

¿Por qué dos bucles de jerarquía?

El Bucle 1 (changeset ↔ outer) dispara la publicación del changeset y establece el contexto de administrador. El Bucle 2 (re-entry ↔ inner) se dispara durante la ventana de administrador (dentro de la llamada save() del ajuste de elemento de menú de navegación en el bucle de publicación del changeset) y activa parse_request → re-entrada REST. Los bucles son independientes porque la corrección del Bucle 2 debe escribir el post de re-entrada en la BD durante la ventana de administrador en la línea 3581 — antes del reinicio en la línea 3589.

Descargo de responsabilidad

Esta herramienta se publica con fines de investigación y pruebas de seguridad autorizadas. Úsala solo contra sistemas que poseas o para los que tengas autorización escrita explícita para probar. El acceso no autorizado a sistemas informáticos es ilegal.

Créditos

Investigación y desarrollo por CryptoCat.

Descargar herramienta
  • La comprobación de jerarquía ($post_id distinto de cero) detecta el Bucle 2 (re-entry ↔ inner)
  • La corrección llama a wp_update_post(re-entry) → escribe type=request, status=parse en la BD
  • wp_transition_post_status dispara do_action("parse_request") → rest_api_loaded() → serve_request()
  • La API REST re-entra y reprocesa todo el batch con privilegios de administrador
  • POST /wp/v2/users en la cola tiene éxito → se crea el administrador → die()
  • RequisitoPor qué¿WP por defecto?
    Al menos un post publicadooEmbed necesita una URL local para activar el procesamiento de embedSí (Hello World)
    Sin caché de objetos persistenteSplit-the-query debe estar deshabilitado para que las filas UNION sobrevivanSí (caché de archivos por defecto)
    API REST accesibleLa re-entrada vía parse_request necesita el servidor RESTSí
    Escritura directa en el sistema de archivosLa subida del plugin necesita FS_METHOD=direct o que PHP sea propietario de wp-contentSí (la mayoría de hosts)