The Exploit
Attacker needs an authenticated WordPress account with Subscriber-level access or higher.
curl 'https://target.example.com/wp-admin/admin-ajax.php' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'Cookie: wordpress_logged_in_<hash>=<session>' \
--data 'action=change_wallet_fund_request_status&requested_user_id=1&status=approved'
This request submits requested_user_id=1 and status=approved to the plugin AJAX handler. The vulnerable endpoint responds as if the approval succeeded and the wallet request processing logic runs for the arbitrary user ID, allowing the attacker to approve or modify another user's wallet fund request.
What the Patch Did
Before:
if ( 'approved' == $status ) {
$requesting_user_wallet = get_user_meta( $requesting_user_id, 'wps_wallet', true );
// ... processing without authorization check
}
After:
if ( $requested_user_id != $user_id ) {
$wps_wsfw_error_text = esc_html__( 'You are not authorized to perform this action', 'wallet-system-for-woocommerce' );
$message = array(
'msg' => $wps_wsfw_error_text,
'msgType' => 'error',
);
} else {
if ( 'approved' == $status ) {
// ... processing now protected by authorization check
}
}
The patch adds an explicit ownership/authorization check: the code now rejects requests unless the POSTed requested_user_id matches the current authenticated user's ID. That prevents an attacker from approving or manipulating requests for anyone other than themselves.
Root Cause
This is an authorization bypass in the AJAX handler, matching CWE-639: Authorization Bypass Through User-Controlled Key. The attacker-supplied POST field requested_user_id reaches the approval logic unchecked. When status=approved, the code processes the wallet request for the arbitrary requested_user_id without verifying that the authenticated user is allowed to act on that ID.
Why It Works
The load-bearing fix is the if ( $requested_user_id != $user_id ) gate. Without that comparison, the vulnerable branch still executes for any requested_user_id supplied by the attacker. The surrounding else block is what contains the sensitive approval processing; the new error branch is just the rejection path. The patch also uses esc_html__() to build an error message, but that is secondary— the real defence is the explicit authorization check on user identity.
Hardening Checklist
- Enforce ownership checks on user-specific actions: compare
get_current_user_id()with the POSTedrequested_user_id. - Use
wp_verify_nonce()for AJAX callbacks to ensure the request originates from a valid session. - Require capability checks for sensitive actions with
current_user_can()when appropriate. - Sanitize incoming values: use
absint( $_POST['requested_user_id'] )andsanitize_text_field( $_POST['status'] ). - Validate the target user with
get_userdata( $requested_user_id )before operating on wallet metadata.
References
- https://nvd.nist.gov/vuln/detail/CVE-2025-14450