
CVE-2026-8206 : Kirki Customizer Framework - Prise de contrôle de compte non authentifiée (CVSS 9.8)
CVSS v3.1 9.8 / 10.0 CRITIQUE
Vecteur : AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CVE-2026-8206 est une vulnérabilité de prise de contrôle de compte non authentifiée dans Kirki Customizer Framework — un plugin WordPress populaire de création de pages avec plus de 90 000 installations actives. La vulnérabilité permet à un attaquant de prendre le contrôle de n'importe quel compte WordPress (y compris celui d'un administrateur) en envoyant simplement une seule requête HTTP.
Le bug réside dans le point de terminaison REST API kirki-forgot-password du module ComponentLibrary. Ce point de terminaison permet de demander la réinitialisation du mot de passe de n'importe quel utilisateur et envoie le lien de réinitialisation à une adresse e-mail spécifiée par l'attaquant, au lieu de l'adresse e-mail enregistrée de l'utilisateur.
| Information | Détails |
|---|---|
| Plugin | Kirki Customizer Framework (WordPress Page Builder) |
| Versions concernées | 6.0.0 – 6.0.6 |
| Version corrigée | 6.0.7 (ou 6.0.12+) |
| Type de vulnérabilité | Prise de contrôle de compte non authentifiée via détournement de l'e-mail de réinitialisation de mot de passe |
| Point de terminaison | /index.php?rest_route=/KirkiComponentLibrary/v1/kirki-forgot-password (ou /wp-json/KirkiComponentLibrary/v1/kirki-forgot-password) |
| CVSS | 9.8 CRITIQUE |
Conditions préalables à l'exploitation :
→ Chaque site WordPress avec Kirki 6.0.0–6.0.6 installé est vulnérable par défaut.
Kirki enregistre des points de terminaison REST API pour la fonctionnalité Component Library, notamment login, register, forgot-password et change-password. Tous autorisent l'accès invité (guest_permissions_check renvoie true).
Le nonce WordPress est un jeton de protection CSRF. Kirki exige un nonce via l'en-tête X-WP-Element-Nonce ou vérifie un paramètre nonce dans la requête. Cependant, le nonce peut être généré/extrait publiquement côté client.
WordPress utilise get_password_reset_key() pour générer un jeton de réinitialisation de mot de passe. Ce jeton est inclus par Kirki dans le modèle d'e-mail avant l'envoi.
Fichier : ComponentLibrary/controller/CompLibFormHandler.php
Dans la version vulnérable (6.0.0–6.0.6), la fonction handle_forgot_password() ne vérifie pas si l'e-mail fourni correspond à l'e-mail enregistré de l'utilisateur. Un attaquant peut spécifier le nom d'utilisateur de la victime et son propre e-mail — le lien de réinitialisation sera envoyé à l'e-mail de l'attaquant.
Code source vulnérable :
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
}
L'objet et le corps de l'e-mail sont reçus du client via les paramètres emailSubject et emailBody sans aucune signature HMAC pour vérifier leur intégrité. L'attaquant peut modifier arbitrairement le contenu de l'e-mail envoyé.
// 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 );
}
| Facteur | Explication |
|---|---|
| Aucune authentification requise | guest_permissions_check() renvoie true — n'importe qui peut appeler le point de terminaison |
| N'importe quel compte peut être compromis | Seul le nom d'utilisateur est nécessaire (par défaut : 'admin') — aucun e-mail/mot de passe requis |
| Impact : prise de contrôle admin | Prise de contrôle admin → upload de webshell → RCE → contrôle total du serveur |
| Large diffusion | Kirki est un plugin largement intégré dans de nombreux thèmes WordPress |
L'exploitation de CVE-2026-8206 part d'un accès nul — aucun compte, aucun mot de passe, aucune session — jusqu'à la prise de contrôle totale du compte admin en utilisant uniquement des requêtes 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
Téléchargez le plugin depuis wordpress.org et ouvrez directement le fichier 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 lecture du code révèle : emailBody doit être un tableau JSON, chaque élément possède type et value. Si type = chip et value = reset_link, Kirki insère automatiquement le lien de réinitialisation dans l'e-mail.
Effectuez une recherche grep directement dans le répertoire du plugin :
grep -r "emailBody" wp-content/plugins/kirki/assets/js/
grep -r "reset_link" wp-content/plugins/kirki/assets/js/
Le code frontend compilé révélera la structure JSON exacte requise.
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