
Exploit de preuve de concept pour CVE-2026-5076 démontrant la prise de contrôle d'un compte administrateur sans authentification dans ARMember Premium via une injection SQL et le stockage en clair de la clé de réinitialisation de mot de passe.
Clés de réinitialisation de mot de passe en clair stockées dans la base de données + injection SQL = prise de contrôle complète de l'administrateur
| Élément | Détail |
|---|---|
| CVE ID | CVE-2026-5076 |
| Plugin | ARMember – Plugin d'adhésion et de restriction de contenu |
| Version affectée | Premium <= 7.3.1 |
| Version corrigée | 7.3.2 |
| Score CVSS | 9.8 Critical |
| CWE | CWE-640: Weak Password Recovery |
| Type | Réinitialisation de mot de passe non sécurisée → stockage de clé en clair |
| Vecteur d'attaque | Réseau / Distant / Non authentifié (via chaîne SQLi) |
| Installations actives | 30,000+ (Premium) |
| Découvreur | Wordfence Threat Intelligence |
| Date de publication | 3 juin 2026 |
| CVE | Type | Sévérité |
|---|---|---|
| CVE-2026-5076 | Réinitialisation de mot de passe non sécurisée — stockage de clé en clair | 9.8 Critique |
| CVE-2026-5073 | Injection SQL non authentifiée (ORDER BY) | 9.8 Critique |
| CVE-2026-5074 | Injection SQL non authentifiée (WHERE) | 9.8 Critique |
Trois vulnérabilités critiques dans le plugin WordPress ARMember Premium <= 7.3.1 qui, enchaînées ensemble, permettent la prise de contrôle totale du compte administrateur sans authentification :
wp_usermeta (arm_reset_password_key), et non hachée comme le standard WordPressorder du gestionnaire AJAX arm_directory_paging_action()filter du gestionnaire AJAX arm_directory_paging_action()Conséquence directe de CVE-2026-5076 : Toute personne ayant un accès en lecture à la base de données (via SQLi, exposition de sauvegardes, etc.) peut lire la clé de réinitialisation de mot de passe en clair et l'utiliser immédiatement pour réinitialiser le mot de passe de n'importe quel compte — sans avoir besoin de la casser.
WordPress standard stocke la clé de réinitialisation de mot de passe dans la colonne user_activation_key sous forme hachée à l'aide de wp_hash(). ARMember stocke une copie de la même clé dans wp_usermeta avec la meta_key arm_reset_password_key — mais en CLAIR :```php
// FILE: armember-membership/core/class.arm_member_forms.php
// Fungsi: arm_lost_password_action()
// WordPress menyimpan HASHED key (aman) $key = wp_generate_password(20, false); $wp_key = $wpdb->get_var( $wpdb->prepare("SELECT user_activation_key FROM $wpdb->users WHERE user_login=%s", $user_login) );
// ARMember menyimpan PLAINTEXT key (VULNERABLE!) update_user_meta($user_id, 'arm_reset_password_key', $wp_key); // ^^^^^^ // Ini adalah key ASLI yang bisa langsung dipakai
**Problème critique** : `$wp_key` est la clé générée par `wp_generate_password(20, false)` — 20 caractères alphanumériques. WordPress hache cette clé avant de la stocker dans `user_activation_key`, mais ARMember la stocke **avant le hachage** ou conserve une copie séparée **non hachée**.
### Cause racine n°2 : bug de persistance de la clé
Lorsque `get_password_reset_key()` est appelé (noyau WordPress), une nouvelle clé est générée et hachée. Cependant, cette fonction **ne met PAS à jour** `arm_reset_password_key` :```php
// WordPress core: get_password_reset_key($user)
// - Generate key baru
// - Hash key → simpan di user_activation_key
// - Return key plaintext
// - TAPI: arm_reset_password_key TIDAK diupdate!
Ainsi, l’ancienne clé en clair reste stockée indéfiniment dans arm_reset_password_key même après que l’utilisateur a réinitialisé son mot de passe. Cette clé peut être réutilisée plusieurs fois jusqu’à ce que la méta-clé soit explicitement supprimée.
Le gestionnaire AJAX arm_directory_paging_action() vérifie le nonce via arm_check_user_cap(), mais les paramètres order et filter sont directement intégrés à la requête SQL sans assainissement :```php
// FILE: armember-membership/core/class.arm_member_forms.php
// Fungsi: arm_directory_paging_action()
// Nonce check (dibutuhkan nonce valid) $nonce_check = $this->arm_check_user_cap();
// ORDER BY injection — langsung ke SQL tanpa sanitasi! $orderby = "u.{$arm_member} {$order_dir}"; // $order_dir dari $_POST['order'] → LANGSUNG ke ORDER BY
// WHERE injection via filter if (!empty($filter)) { $where .= " AND " . $filter; // ← LANGSUNG concatenation! }
**Exploitation ORDER BY**: Le paramètre `order` est inséré dans la clause `ORDER BY` SQL. Comme il n'y a pas de sanitisation, un attaquant peut injecter une sous-requête :```sql
-- Payload SQLi via parameter order
ORDER BY u.ID ASC, IF(COND, 1, EXP(710))
-- COND = TRUE → IF returns 1 → ORDER BY 1 → response normal (besar)
-- COND = FALSE → IF returns EXP(710) → MySQL overflow ERROR → response 90B
Cet oracle est insensible à la latence réseau car il distingue TRUE/FALSE en fonction de l'erreur ou du succès, et non du temps de réponse.
╔══════════════════════════════════════════════════════════════════════════════════╗ ║ CVE-2026-5076 FULL CHAIN ATTACK ROADMAP ║ ║ ARMember Premium <= 7.3.1 → Unauthenticated Admin Takeover ║ ╚══════════════════════════════════════════════════════════════════════════════════╝