User Profile Builder <= 3.15.1 - Unauthenticated Privilege Escalation via Account Takeover
Description
The User Profile Builder – Beautiful User Registration Forms, User Profiles & User Role Editor plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 3.15.1. 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.15.1Source Code
WordPress.org SVN# Research Plan: CVE-2025-15030 - Profile Builder Account Takeover ## 1. Vulnerability Summary The **User Profile Builder** plugin (<= 3.15.1) contains a critical privilege escalation vulnerability. The core issue lies in the front-end profile editing logic, specifically within the `wppb_edit_profi…
Show full research plan
Research Plan: CVE-2025-15030 - Profile Builder Account Takeover
1. Vulnerability Summary
The User Profile Builder plugin (<= 3.15.1) contains a critical privilege escalation vulnerability. The core issue lies in the front-end profile editing logic, specifically within the wppb_edit_profile_handler (or similar handler for the [wppb-edit-profile] shortcode). The plugin fails to verify two critical things before updating a user's password:
- Authentication: It does not ensure the request is from a logged-in user.
- Authorization (IDOR): It does not ensure that the user ID being updated (
user_idparameter) matches the ID of the currently authenticated user.
This allows an unauthenticated attacker to change the password of any user, including administrators, by knowing their user ID (typically 1 for the first admin).
2. Attack Vector Analysis
- Endpoint: Any page containing the
[wppb-edit-profile]shortcode, or theadmin-ajax.phpendpoint if the handler is registered there. - Method:
POST - Parameters:
user_id: The ID of the target user (e.g.,1).pass1: The new password.pass2: Confirmation of the new password.wppb_edit_profile: The nonce variable required to pass the security check.action: (If AJAX)wppb_edit_profile.
- Preconditions:
- An administrator user must exist (default ID 1).
- A page with the
[wppb-edit-profile]shortcode must be active, OR the attacker must be able to trigger theinithook handler.
3. Code Flow
- Entry Point: During the
initorwp_loadedhook, the plugin initializesassets/lib/wppb/front-end/edit-profile.php. - Hook Registration: The function
wppb_edit_profile_handler()is registered to handle profile updates. - Vulnerable Logic:
- The function checks if
$_POST['wppb_edit_profile'](the nonce) is set. - It verifies the nonce using
wp_verify_nonce( $_POST['wppb_edit_profile'], 'wppb_edit_profile_nonce' ). - The Flaw: If the nonce is valid for the current user session (even for an unauthenticated user ID 0), the code proceeds.
- It retrieves the target user ID from
$_POST['user_id']or$_GET['edit_user']. - It calls
wp_update_user()orwp_set_password()using thepass1value from$_POST. - There is no check like
if ( ! is_user_logged_in() )orif ( $current_user_id !== $target_user_id )before the password update occurs.
- The function checks if
4. Nonce Acquisition Strategy
The plugin uses nonces to protect form submissions. To exploit this unauthenticated, we need a nonce generated for a logged-out user (UID 0).
- Identify Shortcode: The plugin uses
[wppb-edit-profile]. - Create Test Page:
wp post create --post_type=page --post_title="Edit Profile" --post_status=publish --post_content='[wppb-edit-profile]' - Extract Nonce:
- Navigate to the newly created page. Even if the page content says "You must be logged in," the plugin may still enqueue scripts or hidden fields containing nonces.
- Use
browser_evalto look for the nonce in the localized JS or the form:// Check for common PB JS objects window.wppb_ajax_nonce || document.querySelector('input[name="wppb_edit_profile"]')?.value - The specific key is likely
wppb_edit_profile(the input name) and the action used to create it iswppb_edit_profile_nonce.
5. Exploitation Strategy
- Target User ID: Identify the admin ID (usually
1). - Request Construction:
- URL: The URL of the page containing the
[wppb-edit-profile]shortcode. - Headers:
Content-Type: application/x-www-form-urlencoded - Payload:
wppb_edit_profile=[NONCE]&user_id=1&pass1=Password123!&pass2=Password123!&action=wppb_edit_profile
- URL: The URL of the page containing the
- Execution: Use
http_requestto send the payload. - Success Indicator: A redirect or a success message indicating the profile was updated, and subsequent inability to login with the old admin password.
6. Test Data Setup
- Admin User: Ensure an admin user exists (e.g., username
admin, ID1). - Vulnerable Page:
- Create a page with the Edit Profile shortcode:
wp post create --post_type=page --post_status=publish --post_content='[wppb-edit-profile]'
- Create a page with the Edit Profile shortcode:
- Settings: Ensure "Allow users to edit their profiles" is enabled in Profile Builder settings (usually enabled by default).
7. Expected Results
- The HTTP response should indicate a successful form submission (e.g., HTTP 302 redirect or a 200 OK with "Profile updated" in the body).
- The administrator's password in the database will be changed to
Password123!.
8. Verification Steps
- Check via WP-CLI: Attempt to verify the admin's credentials:
wp user check-password admin Password123! - Database Check: Verify the
user_passhash has changed in thewp_userstable for ID 1. - Login Test: Use
browser_navigateto attempt login at/wp-login.phpwith the new credentials.
9. Alternative Approaches
- AJAX Endpoint: If the shortcode POST fails, try hitting
admin-ajax.phpwithaction=wppb_edit_profileand the same parameters. - Username instead of ID: Some versions of PB might accept
usernameoremailinstead ofuser_id. Try addingusername=adminto the payload. - Password Recovery Bypass: If the
edit-profileroute is strictly blocked for guests, investigateassets/lib/wppb/front-end/recover-password.php. A similar IDOR might exist where thekeyparameter (reset token) is not properly validated, allowing a password change if thekeyis sent as an empty string.
Summary
The User Profile Builder plugin for WordPress is vulnerable to an unauthenticated account takeover because the profile editing handler fails to verify if the requester is authenticated or authorized to update a specific user. By exploiting a publicly accessible nonce, an attacker can submit a password change request for any user ID, including administrative accounts.
Vulnerable Code
// assets/lib/wppb/front-end/edit-profile.php function wppb_edit_profile_handler() { if ( isset( $_POST['wppb_edit_profile'] ) && wp_verify_nonce( $_POST['wppb_edit_profile'], 'wppb_edit_profile_nonce' ) ) { $user_id = ( isset( $_POST['user_id'] ) ) ? intval( $_POST['user_id'] ) : ( ( isset( $_GET['edit_user'] ) ) ? intval( $_GET['edit_user'] ) : 0 ); if ( ! empty( $user_id ) ) { // ... (other profile field updates) if ( ! empty( $_POST['pass1'] ) && $_POST['pass1'] === $_POST['pass2'] ) { wp_set_password( $_POST['pass1'], $user_id ); } } } }
Security Fix
@@ -120,6 +120,15 @@ function wppb_edit_profile_handler() { if ( isset( $_POST['wppb_edit_profile'] ) && wp_verify_nonce( $_POST['wppb_edit_profile'], 'wppb_edit_profile_nonce' ) ) { + if ( ! is_user_logged_in() ) { + return; + } + + $current_user_id = get_current_user_id(); $user_id = ( isset( $_POST['user_id'] ) ) ? intval( $_POST['user_id'] ) : ( ( isset( $_GET['edit_user'] ) ) ? intval( $_GET['edit_user'] ) : 0 ); + + if ( $user_id !== $current_user_id && ! current_user_can( 'edit_users' ) ) { + return; + } + if ( ! empty( $user_id ) ) { if ( ! empty( $_POST['pass1'] ) && $_POST['pass1'] === $_POST['pass2'] ) { wp_set_password( $_POST['pass1'], $user_id );
Exploit Outline
The exploit methodology involves four main steps: 1. Locating a front-end page that contains the [wppb-edit-profile] shortcode. 2. Extracting the 'wppb_edit_profile' nonce value from the page source as an unauthenticated visitor. 3. Submitting a POST request to that page containing the stolen nonce, a target 'user_id' (typically 1 for the default administrator), and new values for 'pass1' and 'pass2'. 4. Because the plugin does not verify that the logged-in user matches the target ID, the wp_set_password function is called for the specified user, allowing the attacker to take over the account.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.