
Proof-of-Concept-Exploit für CVE-2026-5076, der die Übernahme eines Admin-Kontos ohne Authentifizierung in ARMember Premium mittels SQL-Injection und Klartext-Speicherung von Passwort-Reset-Schlüsseln demonstriert.
Klartext-Passwort-Reset-Keys in der Datenbank gespeichert + SQL-Injection = Vollständige Admin-Übernahme
| Punkt | Detail |
|---|---|
| CVE ID | CVE-2026-5076 |
| Plugin | ARMember – Membership Plugin & Content Restriction |
| Betroffene Version | Premium <= 7.3.1 |
| Gepatchte Version | 7.3.2 |
| CVSS Score | 9.8 Kritisch |
| CWE | CWE-640: Schwache Passwortwiederherstellung |
| Typ | Unsicherer Passwort-Reset-Mechanismus → Klartext-Key-Speicherung |
| Angriffsvektor | Netzwerk / Remote / Unauthentifiziert (via SQLi-Kette) |
| Aktive Installationen | 30.000+ (Premium) |
| Entdecker | Wordfence Threat Intelligence |
| Veröffentlichungsdatum | 3. Juni 2026 |
| CVE | Typ | Schweregrad |
|---|---|---|
| CVE-2026-5076 | Unsicherer Passwort-Reset – Klartext-Key-Speicherung | 9.8 Kritisch |
| CVE-2026-5073 | Unaauthentifizierte SQL-Injection (ORDER BY) | 9.8 Kritisch |
| CVE-2026-5074 | Unaauthentifizierte SQL-Injection (WHERE) | 9.8 Kritisch |
Drei kritische Schwachstellen im WordPress-Plugin ARMember Premium <= 7.3.1, die in einer Kette eine vollständige Übernahme eines Administratorkontos ohne Authentifizierung ermöglichen:
wp_usermeta (arm_reset_password_key) gespeichert, nicht gehasht wie im WordPress-Standard.order des AJAX-Handlers arm_directory_paging_action()filter des AJAX-Handlers arm_directory_paging_action()Direkte Konsequenz von CVE-2026-5076: Jeder mit Lesezugriff auf die Datenbank (via SQLi, Backup-Exposition usw.) kann den Passwort-Reset-Key im Klartext lesen und direkt verwenden, um das Passwort eines beliebigen Kontos zurückzusetzen – ohne Knacken.
WordPress speichert den Passwort-Reset-Key standardmäßig in der Spalte user_activation_key in gehashter Form mittels wp_hash(). ARMember speichert eine Kopie desselben Keys in wp_usermeta mit dem Meta-Key arm_reset_password_key – jedoch im KLARTEXT:```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
**Kritisches Problem**: `$wp_key` hier ist der von `wp_generate_password(20, false)` generierte Schlüssel — 20 alphanumerische Zeichen. WordPress hasht diesen Schlüssel vor dem Speichern in `user_activation_key`, aber ARMember speichert ihn **vor dem Hashing** oder speichert eine separate, **nicht gehashte** Kopie.
### Root Cause #2: Key Persistence Bug
Wenn `get_password_reset_key()` aufgerufen wird (WordPress Core), wird ein neuer Schlüssel generiert und gehasht. Diese Funktion **AKTUALISIERT NICHT** `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!
Infolgedessen bleibt der alte Klartextschlüssel dauerhaft in arm_reset_password_key gespeichert, selbst nachdem der Benutzer ein Passwort-Reset durchgeführt hat. Dieser Schlüssel kann wiederholt verwendet werden, bis der Meta-Key explizit gelöscht wird.
Der AJAX-Handler arm_directory_paging_action() hat eine Nonce-Prüfung über arm_check_user_cap(), aber die Parameter order und filter gelangen direkt ohne Sanitisierung in die SQL-Abfrage:```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! }
**Ausnutzung ORDER BY**: Parameter `order` wird in die SQL-Klausel `ORDER BY` eingefügt. Da keine Bereinigung erfolgt, kann ein Angreifer einen Subquery einschleusen:```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
Dieser Oracle ist immun gegen Netzwerklatenz da es TRUE/FALSE basierend auf Fehler vs Erfolg unterscheidet, nicht auf Antwortzeit.
╔══════════════════════════════════════════════════════════════════════════════════╗ ║ CVE-2026-5076 FULL CHAIN ATTACK ROADMAP ║ ║ ARMember Premium <= 7.3.1 → Unauthenticated Admin Takeover ║ ╚══════════════════════════════════════════════════════════════════════════════════╝