
CVE-2026-8206: Kirki Customizer Framework - Toma de control de cuenta no autenticada (CVSS 9.8)
CVSS v3.1 9.8 / 10.0 CRÍTICA
Vector: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2026-8206 es una vulnerabilidad de Toma de control de cuenta no autenticada en Kirki Customizer Framework, un popular plugin de constructor de páginas para WordPress con más de 90.000 instalaciones activas. La vulnerabilidad permite a un atacante tomar el control de cualquier cuenta de WordPress (incluida la de administrador) simplemente enviando una única petición HTTP.
El fallo reside en el endpoint de la API REST kirki-forgot-password del módulo ComponentLibrary. Este endpoint permite solicitar el restablecimiento de contraseña de cualquier usuario y envía el enlace de restablecimiento a una dirección de correo electrónico especificada por el atacante, en lugar de al correo registrado del usuario.
| Información | Detalles |
|---|---|
| Plugin | Kirki Customizer Framework (constructor de páginas WordPress) |
| Versiones afectadas | 6.0.0 – 6.0.6 |
| Parcheada | 6.0.7 (o 6.0.12+) |
| Tipo de vulnerabilidad | Toma de control de cuenta no autenticada mediante secuestro del correo de restablecimiento de contraseña |
| Endpoint | /index.php?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password (o /wp-json/KirkiComponentLibrary/v1/kirki-forgot-password) |
| CVSS | 9.8 CRÍTICA |
Requisitos previos para la explotación:
→ Todo sitio WordPress con Kirki 6.0.0–6.0.6 instalado es vulnerable por defecto.
Kirki registra endpoints de la API REST para la funcionalidad Component Library, incluyendo login, register, forgot-password y change-password. Todos ellos permiten el acceso de invitados (guest_permissions_check devuelve true).
El nonce de WordPress es un token de protección CSRF. Kirki requiere un nonce mediante la cabecera X-WP-Element-Nonce o comprueba un parámetro nonce en la petición. Sin embargo, el nonce puede generarse/extraerse públicamente desde el lado del cliente.
WordPress utiliza get_password_reset_key() para generar un token de restablecimiento de contraseña. Kirki incluye este token en la plantilla del correo antes de enviarlo.
Archivo: ComponentLibrary/controller/CompLibFormHandler.php
En la versión vulnerable (6.0.0–6.0.6), la función handle_forgot_password() no verifica si el correo proporcionado coincide con el correo registrado del usuario. Un atacante puede especificar el nombre de usuario de la víctima y su propio correo: el enlace de restablecimiento se enviará al correo del atacante.
Código fuente vulnerable:
public function handle_forgot_password( $request ) {
$form_data = $request->get_body_params();
$this->validate_nonce( 'kirki-forgot-password' );
$email = $form_data['email']; // <-- ATTACKER CONTROLLED
$username = $form_data['username'];
$user = get_user_by( 'login', $username );
//BUG: Does NOT check $email === $user->user_email
// Attacker's email is used directly
$key = get_password_reset_key( $user );
$reset_link = "$url?action=rp&key=$key&login=$username";
// SENDS RESET LINK TO ATTACKER'S EMAIL
wp_mail( $email, $subject, $body_with_reset_link );
// ^^^^^
// ATTACKER'S EMAIL, NOT THE USER'S EMAIL
}
El asunto y el cuerpo del correo se reciben del cliente a través de los parámetros emailSubject y emailBody sin ninguna firma HMAC que verifique su integridad. El atacante puede modificar arbitrariamente el contenido del correo que se envía.
// VULNERABLE VERSION: No HMAC verification
$email_subject = $form_data['emailSubject']; // Client-controlled
$email_body = $form_data['emailBody']; // Client-controlled
// No verify_email_template_signature()
// PATCHED VERSION (6.0.7+): HMAC verification / Email matching against DB
$user_email = $user->get( 'user_email' );
if ( $email !== $user_email ) {
return new WP_REST_Response( array( 'message' => 'If an account exists...' ), 200 );
}
| Factor | Explicación |
|---|---|
| No se requiere autenticación | guest_permissions_check() devuelve true: cualquiera puede llamar al endpoint |
| Se puede tomar el control de cualquier cuenta | Solo se necesita el nombre de usuario (por defecto: 'admin') — no se requiere correo ni contraseña |
| Impacto: toma de control del administrador | Controlar al administrador → subir webshell → RCE → control total del servidor |
| Amplia distribución | Kirki es un plugin ampliamente integrado en muchos temas de WordPress |
La explotación de CVE-2026-8206 comienza desde cero acceso — sin cuenta, sin contraseña, sin sesión — hasta la toma de control total del administrador utilizando únicamente peticiones HTTP.
Phase 1: Extract Nonce → Obtain nonce from a public page (if required)
Phase 2: Hijack Reset Email → Send reset link to attacker's email
Phase 3: Reset Password → Change admin password using the obtained reset key
Phase 4: Login Admin → Log in with the new password
Phase 5: Webshell Upload → Install backdoor via plugin upload
Phase 6: RCE → Execute arbitrary commands on the server
Descarga el plugin desde wordpress.org y abre directamente el archivo CompLibFormHandler.php:
// The handle_forgot_password function receives data from the client:
$email_subject = isset( $form_data['emailSubject'] )
? sanitize_text_field( $form_data['emailSubject'] ) : '';
$email_body = isset( $form_data['emailBody'] )
? json_decode( $form_data['emailBody'], true ) : '';
// Email body rendering logic:
foreach ( $email_body as $body_data ) {
if ( $body_data['type'] === 'text' ) {
$email_body = $email_body . $body_data['value'];
} elseif ( $body_data['type'] === 'chip' ) {
$email_body = $email_body . $chip_data[ $body_data['value'] ];
// ^^^^^^^^^^^^^^^^^^^
// $chip_data['reset_link'] = actual reset URL!
}
}
→ La lectura del código revela: emailBody debe ser un array JSON; cada elemento tiene type y value. Si type = chip y value = reset_link, Kirki inserta automáticamente el enlace de restablecimiento en el correo.
Busca directamente dentro del directorio del plugin:
grep -r "emailBody" wp-content/plugins/kirki/assets/js/
grep -r "reset_link" wp-content/plugins/kirki/assets/js/
El código frontend compilado revelará la estructura JSON exacta requerida.
Find endpoint (/wp-json/ or rest_route)
↓
Read PHP source → find $form_data['emailBody']
↓
Understand JSON array format (type / value)
↓
Read chip_data array → find key "reset_link"
↓
Build payload: emailBody=[{"type":"chip","value":"reset_link"}]
↓
Test → success