ProfileGrid – User Profiles, Groups and Communities <= 5.9.7.2 - Missing Authorization to Authenticated (Subscriber+) Arbitrary User Suspension
Description
The ProfileGrid – User Profiles, Groups and Communities plugin for WordPress is vulnerable to unauthorized user suspension due to a missing capability check on the pm_deactivate_user_from_group() function in all versions up to, and including, 5.9.7.2. This makes it possible for authenticated attackers, with Subscriber-level access and above, to suspend arbitrary users from groups, including administrators, via the pm_deactivate_user_from_group AJAX action.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
<=5.9.7.2Source Code
WordPress.org SVNThis research plan outlines the process for analyzing and exploiting **CVE-2025-13416** in the ProfileGrid plugin. ### 1. Vulnerability Summary The **ProfileGrid** plugin (up to 5.9.7.2) contains a missing authorization vulnerability in the `pm_deactivate_user_from_group()` function. While the func…
Show full research plan
This research plan outlines the process for analyzing and exploiting CVE-2025-13416 in the ProfileGrid plugin.
1. Vulnerability Summary
The ProfileGrid plugin (up to 5.9.7.2) contains a missing authorization vulnerability in the pm_deactivate_user_from_group() function. While the function may implement a nonce check (CSRF protection), it fails to verify if the requesting user has the administrative capabilities required to suspend other users. This allows any authenticated user (Subscriber level and above) to deactivate or suspend other users—including administrators—from specific groups or the entire platform, depending on the internal logic of the function.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
pm_deactivate_user_from_group - Authentication Required: Authenticated, Subscriber level or higher (
PR:L). - Vulnerable Parameter:
user_id(likely),group_id(likely). - Preconditions:
- The attacker must have a valid Subscriber account.
- The target user must exist.
- A valid nonce for the
pm_deactivate_user_from_groupaction or a general ProfileGrid AJAX nonce must be obtained.
3. Code Flow (Inferred)
- Entry Point: An AJAX request is sent to
admin-ajax.phpwithaction=pm_deactivate_user_from_group. - Hook Registration: The plugin registers the action, likely in
admin/class-profile-magic-admin.phporincludes/class-profile-magic-ajax.php:add_action('wp_ajax_pm_deactivate_user_from_group', 'pm_deactivate_user_from_group'); - Vulnerable Function: The handler
pm_deactivate_user_from_group()is executed. - Missing Check: The function likely calls
check_ajax_referer()(verifying the nonce) but fails to callcurrent_user_can('manage_options')or a similar check. - Sink: The function proceeds to update the user's status in the ProfileGrid database table (e.g.,
wp_pm_usersorwp_pm_group_members) or updates a user meta key (e.g.,pm_user_status) to a value representing "deactivated" or "suspended".
4. Nonce Acquisition Strategy
ProfileGrid typically localizes its AJAX nonces for use in the frontend/admin dashboard.
- Identify Shortcode: ProfileGrid uses shortcodes like
[profilegrid_user_groups]or[profilegrid_group_wall]to render its interface. - Create Trigger Page: Create a page containing a ProfileGrid shortcode to ensure the necessary scripts and nonces are loaded.
wp post create --post_type=page --post_status=publish --post_title="PG Test" --post_content='[profilegrid_user_groups]' - Navigate and Extract: Use the browser to access the page as a Subscriber and extract the nonce from the localized JS object. ProfileGrid often uses the variable name
profile_magic_varsorpm_ajax_object. - Verification of JS Object:
- Search code for localization:
grep -r "wp_localize_script" . - The expected JS access path is likely:
window.profile_magic_vars?.ajax_nonceorwindow.pm_ajax_object?.nonce.
- Search code for localization:
5. Exploitation Strategy
The exploit involves sending a crafted POST request to the AJAX endpoint.
- Target URL:
http://<target-ip>/wp-admin/admin-ajax.php - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Parameters:
action:pm_deactivate_user_from_groupsecurityornonce: (The nonce value extracted in Step 4)user_id: The ID of the user to suspend (e.g.,1for the administrator).group_id: The ID of a group the user belongs to (inferred requirement).
Step-by-step Execution:
- Setup Users: Ensure a target Administrator (ID 1) and an attacker Subscriber (ID 2) exist.
- Setup Group: Create a ProfileGrid group and add the Administrator to it using WP-CLI.
- Get Nonce: Log in as Subscriber, navigate to the page created in Section 4, and use
browser_evalto grab the nonce. - Trigger Suspension: Use
http_requestto send the payload. - Expected Response: A JSON response indicating success (e.g.,
{"success": true}or1).
6. Test Data Setup
- Create Attacker:
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123 - Create Group (Inferred PG CLI/DB):
Ifwp profilegridcommands aren't available, check the database for the groups table (e.g.,wp_pm_groups) and ensure at least one group exists. - Add Admin to Group: ProfileGrid usually stores group memberships in a custom table. Use
wp db queryto insert a record mapping User ID 1 to a valid Group ID.
7. Expected Results
- The AJAX response should return a success code.
- The target user should no longer be able to access group features or, if the deactivation is global, should be blocked from logging in (depending on how ProfileGrid handles "deactivation").
- A "User Suspended" or "User Deactivated" flag should be visible in the database for that user.
8. Verification Steps
- Check User Meta: Check for changes in user status meta.
wp usermeta get 1 pm_user_status(Check if value is 'deactivated') - Check Database Tables:
wp db query "SELECT * FROM wp_pm_group_members WHERE user_id = 1"
Check if a status column (e.g.,is_active) has changed from1to0. - Login Attempt: Attempt to log in as the Administrator (if deactivation is global) and verify if the login is blocked or a ProfileGrid error message is shown.
9. Alternative Approaches
- Different Nonce Actions: If
pm_deactivate_user_from_grouprequires a specific nonce that isn't easily found, search the code for anywp_create_noncecalls that use a very broad action likeprofile-magic-nonceor-1. - Varying Parameters: Some ProfileGrid versions use
uidinstead ofuser_idorgidinstead ofgroup_id. Verify parameter names by grepping the function definition:grep -A 10 "function pm_deactivate_user_from_group" <file_path> - Global Deactivation: If group-specific deactivation fails, look for related actions like
pm_deactivate_userwhich might share the same missing authorization flaw.
Summary
The ProfileGrid plugin for WordPress fails to perform an authorization check in the `pm_deactivate_user_from_group` AJAX handler. This allows any authenticated user, such as a subscriber, to suspend other users—including administrators—from groups or the platform by providing the target's user ID and a valid security nonce.
Security Fix
@@ -124,6 +124,11 @@ function pm_deactivate_user_from_group() { check_ajax_referer('profile-magic-nonce', 'security'); + if (!current_user_can('manage_options')) { + wp_send_json_error('Unauthorized'); + wp_die(); + } + $user_id = isset($_POST['user_id']) ? intval($_POST['user_id']) : 0; $group_id = isset($_POST['group_id']) ? intval($_POST['group_id']) : 0;
Exploit Outline
To exploit this vulnerability, an attacker must first log in with Subscriber-level credentials. By visiting a page where ProfileGrid shortcodes are active, the attacker extracts the necessary security nonce from the localized `profile_magic_vars` or `pm_ajax_object` JavaScript variable. Using this nonce, the attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `pm_deactivate_user_from_group`. By including the `user_id` of a target (such as the site administrator) and a valid `group_id`, the attacker can successfully suspend the target user because the server-side handler lacks a `current_user_can()` check.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.