The Exploit
An authenticated subscriber-level user can change the email address—and by extension, reset the password—of any administrator account.
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: target-wordpress.local
Content-Type: application/x-www-form-urlencoded
Cookie: wordpress_logged_in_[hash]=subscriber_session_token
action=fmwp_submit_form_handler
&form_type=edit-profile
&nonce=valid_nonce_value
&user_id=1
&user_email=attacker%40evil.com
&first_name=Pwned
&last_name=Admin
The attacker observes a 200 response with success: true and the admin email is now controlled by the attacker. The attacker then requests a password reset at /wp-login.php?action=lostpassword, receives the reset link at their controlled email inbox, and gains full administrative access.
What the Patch Did
Before
if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( $_POST['nonce'], 'fmwp-registration' ) ) {
$registration->add_error( 'global', __( 'Security issue, Please try again', 'forumwp' ) );
}
After
if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'fmwp-registration' ) ) {
$registration->add_error( 'global', __( 'Security issue, Please try again', 'forumwp' ) );
}
The patch applies sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) consistently to all nonce verifications and, critically, wraps user_id and other profile fields with absint() before database operations and comparisons. The core fix is the introduction of a strict type check (absint() + !==) in the authorization gate at line 106 of class-profile.php, which ensures that $user->ID is cast to an integer before being compared against the current user's ID, closing a type-juggling bypass.
Root Cause
CWE-20: Improper Input Validation combined with CWE-639: Insecure Direct Object Reference (IDOR).
The user_id parameter enters via $_POST['user_id'] in the AJAX handler fmwp_submit_form_handler. This value is passed directly to get_userdata() without prior sanitization or authorization check. The authorization logic at line 106 of class-profile.php performs a loose comparison ($user->ID != get_current_user_id()) that is vulnerable to type juggling: if $user->ID is retrieved and compared using a non-strict operator, a subscriber passing user_id=1a could potentially bypass the check depending on PHP's type coercion rules. The attacker crosses the trust boundary from "subscriber input" to "admin data modification" without any capability check or secondary authorization.
Why It Works
The load-bearing line is absint( $user->ID ) !== get_current_user_id(). Removing it leaves the original != comparison, which permits type coercion attacks. The engineer also added explicit wp_unslash() calls on all $_POST fields and sanitization on email, login, and name fields—these are defense-in-depth measures that prevent escape-sequence bypasses and ensure input hygiene. However, the strict type cast and strict comparison operator are the line that actually blocks the IDOR: once $user->ID is forced to an integer, it cannot be juggled into matching the subscriber's ID through string tricks, and the authorization check becomes reliable.
Hardening Checklist
- Add capability checks: Before modifying any user's profile, call
current_user_can( 'edit_user', $user->ID )to verify the logged-in user has explicit permission to edit that account. - Use
absint()+ strict comparison on all ID parameters: Cast all user/post/term IDs to integers withabsint()and use strict comparison (===/!==) to prevent type juggling bypasses. - Implement per-action nonce verification with scope: Use
wp_verify_nonce()with action-specific nonce names (e.g.,'edit-profile-' . $user->ID) to prevent nonce reuse across different profile edit requests. - Sanitize and unslash all
$_POSTfields before use: Applywp_unslash()beforesanitize_*()functions to handle magic quotes, and usesanitize_email(),sanitize_user(), andsanitize_text_field()for each field type. - Log privilege escalation attempts: Add
error_log()or a database audit table entry whenever a user attempts to modify another user's email or password, enabling detection of IDOR probing.
References
- https://nvd.nist.gov/vuln/detail/CVE-2024-8428