Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
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.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
wordpress-cve-2026-63030 — Pre-auth RCE en WordPress Core a través de confusión de rutas batch de REST API + SQLi en WP_Query (CVE-2026-63030 / CVE-2026-60137). PoC de detección. | Kitploit
Herramientas/GitHubGitHub/senanfurkan/wordpress-cve-2026-63030
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónDesarrollo de Payloads
GitHubsenanfurkan/wordpress-cve-2026-63030

wordpress-cve-2026-63030

Pre-auth RCE en WordPress Core a través de confusión de rutas batch de REST API + SQLi en WP_Query (CVE-2026-63030 / CVE-2026-60137). PoC de detección.

Ver Repositorio
449hace 3 mesesAú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

WordPress REST API Confusión de Rutas Batch + Inyección SQL → RCE

Pre-autenticación, sin autenticación, no se requieren plugins. Funciona contra una instalación estándar de WordPress a través del endpoint batch de la API REST.

CVECVE-2026-63030 (confusión de rutas → RCE) + CVE-2026-60137 (SQLi)
GHSAGHSA-ff9f-jf42-662q · GHSA-fpp7-x2x2-2mjf
DescubridorAdam Kues — Assetnote / Searchlight Cyber (denominado "wp2shell")
AfectadoWordPress 6.9.0 – 6.9.4, 7.0.0 – 7.0.1 (cadena RCE completa) · 6.8.0 – 6.8.5 (solo SQLi)
Parcheado6.8.6, 6.9.5, 7.0.2, 7.1-beta2
CVSSCrítico (cadena RCE) / Moderado (SQLi independiente)
Blog del investigadorhttps://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/

1. Resumen

Dos errores en el núcleo de WordPress, encadenables para lograr ejecución remota de código sin autenticación:

  1. Inyección SQL en WP_Query cuando author__not_in es una cadena en lugar de un array — se omite la comprobación de saneamiento is_array() y el valor en bruto se interpola directamente en una cláusula NOT IN (...).
  2. Confusión de rutas batch en WP_REST_Server::serve_batch_request_v1() — las sub-solicitudes WP_Error se insertan en $validation[] pero no en $matches[], provocando un desplazamiento de índice de +1. La sub-solicitud i termina siendo despachada con el manejador de la sub-solicitud i+1.

Ningún error por sí solo es suficiente: la API REST sanitiza author_exclude (type: array, items: integer) antes de que llegue a WP_Query, y el endpoint batch rechaza sub-solicitudes GET (enum: POST, PUT, PATCH, DELETE). Encadenándolos a través de una doble confusión se saltan ambas defensas y se alcanza la SQLi sin autenticación.

2. Causas raíz

2.1 Inyección SQL — src/wp-includes/class-wp-query.php (CVE-2026-60137)

Vulnerable (6.9.4):

if ( ! empty( $query_vars['author__not_in'] ) ) {
    if ( is_array( $query_vars['author__not_in'] ) ) {   // cadena → omitido
        $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
        sort( $query_vars['author__not_in'] );
    }
    $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
    $where         .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
}

Cuando author__not_in es una cadena, la rama is_array() se omite; (array) "payload" se evalúa como ["payload"]; implode(',', ...) devuelve la cadena en bruto, que se interpola directamente en la consulta SQL.

Corrección (6.9.5): usar wp_parse_id_list() que acepta cualquier forma de entrada y devuelve una lista de enteros sanitizada.

2.2 Confusión de rutas batch — src/wp-includes/rest-api/class-wp-rest-server.php (CVE-2026-63030)

// Bucle de validación
foreach ( $requests as $single_request ) {
    if ( is_wp_error( $single_request ) ) {
        $has_error    = true;
                       // ❌  $matches[] NO se añade
        $validation[] = $single_request;
        continue;
    }
    $match     = $this->match_request_to_handler( $single_request );
    $matches[] = $match;
    ...
}

// Bucle de despacho  —  indexa $matches[$i] con el $i ORIGINAL
foreach ( $requests as $i => $single_request ) {
    ...
    $match = $matches[ $i ];          // ← desviado en uno tras un WP_Error
    list( $route, $handler ) = $match;
    $result = $this->respond_to_request( $single_request, $route, $handler, $error );
}

Una sola sub-solicitud WP_Error (por ejemplo, ruta mal formada) en la posición 0 desplaza cada entrada subsiguiente en uno. La solicitud i se despacha entonces con el manejador de la solicitud i+1.

Corrección (6.9.5): también se añade $matches[] = $single_request; para el caso de error. Endurecimiento adicional: se interrumpe rest_api_loaded() / serve_request() mientras ya hay un despacho en curso.

3. La cadena de doble confusión

┌──────────────────────────────────────────────────────────────────────┐
│  LOTE EXTERNO  (POST /wp-json/batch/v1)                             │
│                                                                     │
│  [0]  path = "http://"          → WP_Error, NO en $matches          │
│  [1]  path = "/wp/v2/categories" → lleva lote anidado en el body    │
│         body = { "name": "x",                                       │
│                   "requests": [ LOTE_INTERNO ] }                     │
│         Validado contra categories → campo "requests" intacto       │
│  [2]  path = "/batch/v1"        → manejador batch → desplaza a [1]  │
│                                                                     │
│  Desplazamiento externo: request[1] despachado con el manejador de  │
│  request[2] = serve_batch_request_v1. El endpoint batch NO tiene    │
│  permission_callback → se ejecuta sin autenticación. El body de     │
│  request[1] fue validado contra la ruta *categories*, por lo que    │
│  las sub-solicitudes anidadas NUNCA se verificaron contra el enum   │
│  de métodos batch → las sub-solicitudes internas pueden usar GET.   │
├──────────────────────────────────────────────────────────────────────┤
│  LOTE INTERNO  (procesado dentro de serve_batch_request_v1)         │
│                                                                     │
│  [0]  path = "http://"          → WP_Error, NO en $matches          │
│  [1]  GET /wp/v2/categories                                      │
│         ?author_exclude=<SQLi_PAYLOAD>                             │
│       Validado contra categories → author_exclude NO sanitizado     │
│  [2]  GET /wp/v2/posts          → manejador get_items → desplaza a [1]│
│                                                                     │
│  Desplazamiento interno: inner[1] despachado con el manejador de    │
│  inner[2] = WP_REST_Posts_Controller::get_items. La cadena no       │
│  sanitizada author_exclude se mapea a author__not_in y se pasa a    │
│  WP_Query → INYECCIÓN SQL.                                          │
└──────────────────────────────────────────────────────────────────────┘

El fragmento SQL resultante es:

AND wp_posts.post_author NOT IN ( 1) OR SLEEP(N)-- - )

SLEEP(N) se ejecuta una vez por cada fila de post que coincida, por lo que el retardo total es aproximadamente N × <número_de_posts_publicados> segundos.

De SQLi a RCE ("wp2shell")

La inyección solo SELECT (sin consultas apiladas, $wpdb usa mysqli_query) aún produce RCE en pilas LAMP típicas cuando el usuario de MySQL tiene privilegio FILE, lo cual es predeterminado en muchos hostings compartidos y servidores autogestionados:

1) UNION SELECT 0x3C3F70687020...3F3E INTO OUTFILE '/var/www/html/x.php'/*

escribe una webshell PHP en la raíz web, accesible en /x.php.

Vías alternativas (sin necesidad de privilegio FILE) incluyen leer el hash de la contraseña de administrador mediante SQLi UNION/booleana ciega y subir un plugin malicioso a través de la interfaz de administración autenticada.

4. Detección / PoC

uso: poc_wp_batch_sqli.py [-h] -t TARGET [--sleep SLEEP]
                            [--confusion-only] [--no-color] [-v]

El PoC realiza dos pruebas no destructivas:

PruebaMétodo¿Seguro?
Confusión de rutas (CVE-2026-63030)Estructural — verifica que la solicitud interna[1] (categories) se despacha con el manejador de posts comprobando si el cuerpo de la respuesta contiene campos solo de postsSí
SQLi (CVE-2026-60137)Ciega basada en tiempo — inyecta SLEEP(N) vía author_exclude y mide la latencia frente a una línea base benignaSí
Descargar herramienta