CVE-2026-1644

WP Frontend Profile <= 1.3.8 - Cross-Site Request Forgery to Unauthorized User Account Approval or Rejection

mediumCross-Site Request Forgery (CSRF)
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
1.3.9
Patched in
1d
Time to patch

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:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
Required
Scope
Unchanged
None
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=1.3.8
PublishedMarch 6, 2026
Last updatedMarch 6, 2026
Affected pluginwp-front-end-profile

What Changed in the Fix

Changes introduced in v1.3.9

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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 in inc/class-wp-frontend-profile.php).
  • Endpoint: WordPress Admin Dashboard.
  • Hook: Likely admin_init or init.
  • HTTP Method: Likely GET (as many simple account management actions are handled via query parameters) or POST.
  • Target Parameters:
    • page: wpfep-settings
    • wpfep_action: The action to perform (e.g., approve, reject, or delete).
    • 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

  1. Entry Point: The plugin registers an admin settings page with the slug wpfep-settings (seen in wp-frontend-profile.php via plugin_action_links).
  2. Hook Registration: Within inc/class-wp-frontend-profile.php (referenced in the main file), a hook (likely admin_init) triggers the update_action method.
  3. Logic:
    • The code checks if the page parameter matches wpfep-settings.
    • It checks if wpfep_action and id are set in the $_GET or $_POST superglobals.
    • It likely verifies the current user's capability using current_user_can('manage_options').
  4. Vulnerability: The function fails to call check_admin_referer() or wp_verify_nonce().
  5. Sink: Based on the wpfep_action value, the plugin updates the user's status or meta (e.g., changing a wpfep_user_status meta 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:

  1. Identify Action Parameters:
    The agent must first verify the exact parameter names and the admin page URL. It will grep for update_action and wpfep_action.
    grep -rn "update_action" wp-content/plugins/wp-front-end-profile/
    
  2. Create a Pending User:
    Register a new user through the plugin's frontend registration form.
  3. Admin Simulation:
    Using the http_request tool with Administrator cookies, trigger the update_action function.

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

  1. 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
    
  2. 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]'
    
  3. Register Victim User:
    Navigate to the registration page and create a user (e.g., attacker_user).
  4. 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 Redirect back to the settings page or a 200 OK with a success message.
  • The user account status for attacker_user is 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

  1. Check User Meta:
    Verify that the metadata associated with approval has changed.
    wp user meta list [PENDING_USER_ID]
    
  2. 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
    
  3. 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.

Research Findings
Static analysis — not yet PoC-verified

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

--- /home/deploy/wp-safety.org/data/plugin-versions/wp-front-end-profile/1.3.8/functions/wpfep-functions.php	2024-05-20 13:12:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/wp-front-end-profile/1.3.9/functions/wpfep-functions.php	2026-02-21 21:44:42.000000000 +0000
@@ -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.