CVE-2026-2488

ProfileGrid <= 5.9.8.1 - Missing Authorization to Authenticated (Subscriber+) Arbitrary Message Deletion

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
5.9.8.2
Patched in
1d
Time to patch

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

Technical Details

Affected versions<=5.9.8.1
PublishedMarch 6, 2026
Last updatedMarch 7, 2026

What Changed in the Fix

Changes introduced in v5.9.8.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# 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_msg
    • mid: The ID of the message to be deleted (integer).
    • _wpnonce or nonce: A valid AJAX nonce.
  • Authentication Level: Authenticated (Subscriber+).
  • Precondition: The attacker must know or guess the mid of the target message.

3. Code Flow

  1. Entry Point: An authenticated user sends a POST request to admin-ajax.php with the action pg_delete_msg.
  2. Hook Registration: The plugin registers the AJAX handler (likely in a class like PM_Messaging or Profile_Magic_Public) using add_action('wp_ajax_pg_delete_msg', 'pg_delete_msg').
  3. Execution:
    • The pg_delete_msg() function is invoked.
    • It retrieves the mid parameter 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 provided mid without verifying if the current_user_id() is either the sender or the recipient of that specific message.
  4. 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 (or pg_msg_object in 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

  1. Trigger Loading: The messaging scripts and nonces are enqueued on the User Profile page or pages containing the ProfileGrid messaging shortcode.
  2. 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]'
    
  3. Login & Navigate: Log in as the Subscriber (Attacker) and navigate to the created page.
  4. Extract: Use browser_eval to grab the nonce:
    window.pm_ajax_object?.nonce || window.pg_msg_object?.nonce
    

5. Exploitation Strategy

Step-by-Step Plan

  1. Discovery: Use WP-CLI to identify the messaging table and a target message ID belonging to another user.
  2. Setup: Log in as an attacker (Subscriber).
  3. Nonce: Obtain the ajax-nonce from the frontend using the strategy in Section 4.
  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]
    
  5. Validation: Check the database to confirm the message with mid is gone.

6. Test Data Setup

  1. Users:
    • victim_user (Subscriber)
    • attacker_user (Subscriber)
  2. 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 (likely wp_pg_messages).
    • Use wp db query to 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')"
    
  3. 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 1 or a JSON success message).
  • Database State: A query for the mid should return zero results.
  • Frontend State: The message should no longer appear in the "Victim's" inbox/outbox.

8. Verification Steps

  1. WP-CLI Database Check:
    # Query the messaging table for the deleted ID
    wp db query "SELECT * FROM wp_pg_messages WHERE mid = [TARGET_MID]"
    
    Expected: Empty result.
  2. 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 mid fails, check pg-messaging.js or the plugin source for other ID parameters like message_id or tid (thread ID).
  • Global Nonce: If ajax-nonce is rejected, look for a more specific messaging nonce that might be localized within pg_msg_object.
  • Direct Hook Execution: If the AJAX action name pg_delete_msg is incorrect, grep the full plugin directory for wp_ajax_ to find the exact registered action:
    grep -r "wp_ajax_" . | grep "delete"
    
Research Findings
Static analysis — not yet PoC-verified

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

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/profilegrid-user-profiles-groups-and-communities/5.9.8.1/admin/class-profile-magic-access-options.php /home/deploy/wp-safety.org/data/plugin-versions/profilegrid-user-profiles-groups-and-communities/5.9.8.2/admin/class-profile-magic-access-options.php
--- /home/deploy/wp-safety.org/data/plugin-versions/profilegrid-user-profiles-groups-and-communities/5.9.8.1/admin/class-profile-magic-access-options.php	2026-02-11 04:48:30.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/profilegrid-user-profiles-groups-and-communities/5.9.8.2/admin/class-profile-magic-access-options.php	2026-02-18 10:48:28.000000000 +0000
@@ -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.