
Эксплойт доказательства концепции для CVE-2026-5076, демонстрирующий захват учетной записи администратора без аутентификации в ARMember Premium через SQL-инъекцию и хранение ключей сброса пароля в открытом виде.
Ключи сброса пароля в открытом виде хранятся в базе данных + SQL-инъекция = Полный захват администратора
| Пункт | Детали |
|---|---|
| CVE ID | CVE-2026-5076 |
| Плагин | ARMember – Membership Plugin & Content Restriction |
| Подверженные версии | Premium <= 7.3.1 |
| Исправленная версия | 7.3.2 |
| Оценка CVSS | 9.8 Critical |
| CWE | CWE-640: Weak Password Recovery |
| Тип | Ненадёжный механизм сброса пароля → Хранение ключа в открытом виде |
| Вектор атаки | Network / Remote / Unauthenticated (через цепочку SQLi) |
| Активные установки | 30 000+ (Premium) |
| Обнаружил | Wordfence Threat Intelligence |
| Дата публикации | 3 июня 2026 |
| CVE | Тип | Серьёзность |
|---|---|---|
| CVE-2026-5076 | Ненадёжный сброс пароля — хранение ключа в открытом виде | 9.8 Critical |
| CVE-2026-5073 | Неаутентифицированная SQL-инъекция (ORDER BY) | 9.8 Critical |
| CVE-2026-5074 | Неаутентифицированная SQL-инъекция (WHERE) | 9.8 Critical |
Три критические уязвимости в плагине WordPress ARMember Premium <= 7.3.1, которые при объединении позволяют полностью захватить учётную запись администратора без аутентификации:
wp_usermeta (arm_reset_password_key), а не в хэшированном виде, как это принято в WordPressorder в AJAX-обработчике arm_directory_paging_action()filter в AJAX-обработчике arm_directory_paging_action()Непосредственное следствие CVE-2026-5076: Любой, у кого есть доступ на чтение базы данных (через SQLi, утечку резервных копий и т.д.), может прочитать ключ сброса пароля в открытом виде и сразу же использовать его для сброса пароля любой учётной записи — без необходимости взлома.
Стандартный WordPress хранит ключ сброса пароля в столбце user_activation_key в хэшированном виде с помощью wp_hash(). ARMember хранит ту же копию ключа в wp_usermeta с meta_key arm_reset_password_key — но в открытом виде:```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
**Критическая проблема**: `$wp_key` здесь — это ключ, сгенерированный функцией `wp_generate_password(20, false)` — 20 буквенно-цифровых символов. WordPress хеширует этот ключ перед сохранением в `user_activation_key`, но ARMember сохраняет его **до хеширования** или хранит отдельную копию, которая **не хеширована**.
### Коренная причина #2: Ошибка сохранения ключа
Когда вызывается `get_password_reset_key()` (ядро WordPress), генерируется и хешируется новый ключ. Однако эта функция **НЕ обновляет** `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!
В результате старый открытый ключ навсегда остается сохраненным в arm_reset_password_key даже после того, как пользователь выполнил сброс пароля. Этот ключ можно использовать многократно, пока мета-ключ не будет явно удален.
Обработчик AJAX arm_directory_paging_action() имеет проверку nonce через arm_check_user_cap(), но параметры order и filter напрямую попадают в SQL-запрос без санитации:```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! }
**Эксплуатация ORDER BY**: Параметр `order` передается в предложение `ORDER BY` SQL. Из-за отсутствия санитизации атакующий может внедрить подзапрос:```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
Oracle устойчив к сетевой задержке поскольку различает TRUE/FALSE на основе ошибки и успеха, а не времени отклика.
╔══════════════════════════════════════════════════════════════════════════════════╗ ║ CVE-2026-5076 FULL CHAIN ATTACK ROADMAP ║ ║ ARMember Premium <= 7.3.1 → Unauthenticated Admin Takeover ║ ╚══════════════════════════════════════════════════════════════════════════════════╝