CVE-2026-4653

Block, Suspend, Report for BuddyPress <= 3.6.4 - Authenticated (Subscriber+) Stored Cross-Site Scripting via 'link' Parameter

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
3.6.5
Patched in
1d
Time to patch

Description

The Block, Suspend, Report for BuddyPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'link' parameter in versions up to and including 3.6.4. This is due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers with subscriber-level access and above to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Changed
Low
Confidentiality
Low
Integrity
None
Availability

Technical Details

Affected versions<=3.6.4
PublishedJuly 8, 2026
Last updatedJuly 9, 2026
Affected pluginbp-toolkit

What Changed in the Fix

Changes introduced in v3.6.5

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-4653**, a Stored Cross-Site Scripting (XSS) vulnerability in the "Block, Suspend, Report for BuddyPress" plugin. ### 1. Vulnerability Summary The vulnerability exists in the reporting mechanism of the **Block, Suspend, Report for BuddyPress** plugi…

Show full research plan

This exploitation research plan targets CVE-2026-4653, a Stored Cross-Site Scripting (XSS) vulnerability in the "Block, Suspend, Report for BuddyPress" plugin.

1. Vulnerability Summary

The vulnerability exists in the reporting mechanism of the Block, Suspend, Report for BuddyPress plugin. Authenticated users (Subscribers and above) can submit reports against other members or content. One of the parameters in the report submission, link, is stored in the database (likely as post meta for the report custom post type) and subsequently rendered in the WordPress admin dashboard without sufficient sanitization or escaping. This allows an attacker to inject arbitrary JavaScript that executes when an administrator views the report.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • Action: bptk_report_user or bptk_submit_report (inferred from plugin naming conventions; must be verified via frontend inspection).
  • Vulnerable Parameter: link
  • Authentication: Subscriber level or higher is required.
  • Preconditions: BuddyPress must be active, and the "Report" feature must be enabled in the plugin settings.

3. Code Flow

  1. Submission: A Subscriber user visits a member profile and clicks the "Report" button. This triggers a JavaScript function (likely in the plugin's frontend assets) that collects the report details.
  2. AJAX Request: The script sends an AJAX POST request to admin-ajax.php.
  3. Storage: The server-side handler (likely in a frontend class not provided in the snippets) processes the request. It creates a new post of type report and stores the link parameter into a meta key (e.g., _bptk_reported_item_link) using update_post_meta().
  4. Admin Rendering: An administrator navigates to the "Reports" list or the single report edit screen.
  5. The Sink: The plugin retrieves the stored link meta and echoes it. Based on admin/class-bp-toolkit-reports-screen.php, many columns are rendered by echoing data directly. If the link is displayed in a metabox on the single report page or a custom column in the list table without esc_url() or esc_html(), the payload executes.

4. Nonce Acquisition Strategy

The reporting action requires a WordPress nonce. Because nonces are tied to the user's session, they cannot be generated via WP-CLI for use in HTTP requests.

  1. Identify Trigger: The "Report" button is added to BuddyPress member profiles.
  2. Setup Page: Create a BuddyPress environment with at least two users (Admin and Subscriber).
  3. Navigate: Log in as the Subscriber and navigate to the Admin's profile page (e.g., /members/admin/).
  4. Extract Nonce: The nonce is likely localized in a JavaScript object. Use the browser_eval tool to find it.
    • Common variable names: window.bptk_ajax, window.bsr_data.
    • Common keys: report_nonce, nonce.
    • Command: browser_eval("window.bptk_ajax?.nonce || window.bsr_data?.nonce")
    • Note: Inspect the page source for wp_localize_script calls to find the exact object name.

5. Exploitation Strategy

  1. Identify Action: Use the browser's Network tab (or browser_eval to inspect event listeners) to identify the exact action parameter used when the "Report" button is clicked. We will assume the action is bptk_submit_report (inferred).
  2. Craft Payload: A standard XSS payload for a link context:
    • javascript:alert(document.domain)//
    • "><script>alert(1)</script>
  3. Submit Report: Use the http_request tool to simulate the report submission.

Example Request:

  • Method: POST
  • URL: http://localhost:8080/wp-admin/admin-ajax.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Body:
    action=bptk_submit_report&
    nonce=[EXTRACTED_NONCE]&
    link=javascript:alert('XSS')&
    reported_user_id=1&
    report_type=spam&
    details=Testing+vulnerability
    
    (Note: Parameter names like reported_user_id are inferred and should be verified during the "Identify Action" phase.)
  1. Trigger Execution: Log in as Admin and navigate to the Reports list: /wp-admin/edit.php?post_type=report.

6. Test Data Setup

  1. Activate BuddyPress: Ensure BuddyPress is installed and components (Profiles, Activity) are active.
  2. Activate Plugin: Activate Block, Suspend, Report for BuddyPress.
  3. Create Users:
    • admin (Administrator)
    • victim_sub (Subscriber)
  4. Configure Plugin: Ensure "Report" functionality is enabled in the BSR settings (Dashboard -> BSR -> Report Settings).

7. Expected Results

  • The AJAX request should return a success status (e.g., {"success":true}).
  • A new post of type report should appear in the database.
  • When the Admin views the "Reports" list or clicks into the specific report, the browser should execute the injected JavaScript (e.g., show an alert box).

8. Verification Steps

  1. Check Database: Use WP-CLI to verify the payload is stored.
    wp post list --post_type=report --fields=ID,post_title
    # Get the latest ID, then:
    wp post meta list [ID]
    
  2. Verify Output: Use the http_request tool as Admin to fetch the report list and check for the raw payload in the HTML response.
    # Look for the payload in the response body
    http_request "http://localhost:8080/wp-admin/edit.php?post_type=report"
    

9. Alternative Approaches

  • Payload Context: If the link is reflected inside an <a> tag's href attribute, use javascript:alert(1). If it is reflected inside a value attribute of an <input>, use " autofocus onfocus=alert(1)//.
  • Submission Method: If the plugin uses a REST API endpoint instead of admin-ajax.php, the acquisition of the wp_rest nonce remains the same (extract from wp-json or localized scripts), but the endpoint will change to /wp-json/bptk/v1/report.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Block, Suspend, Report for BuddyPress plugin is vulnerable to Stored Cross-Site Scripting via the 'link' parameter in report submissions. Authenticated attackers with Subscriber-level access can inject malicious web scripts that execute when an administrator views the reports in the WordPress dashboard.

Vulnerable Code

// admin/class-bp-toolkit-reports-screen.php:121
	public function add_report_columns( $column_name, $post_id ) {

		switch ( $column_name ) {

			case 'content' :
				echo get_the_excerpt( $post_id );
				break;

---

// admin/class-bp-toolkit-reports-screen.php:143
			case 'reporter' :
				$user = get_user_by( 'ID', get_post_meta( $post_id, '_bptk_reported_by', true ) );
				echo '<a href="' . get_edit_post_link( $post_id ) . '"><div>' . bp_core_fetch_avatar( array( 'item_id' => $user->ID ) ) . '<div><span>' . $user->display_name . '</span><span>' . get_the_date( "F j, Y \\a\\t g:i a",
						$post_id ) . '</span></div></div></a>';
				break;

			case 'reported' :
				$user = get_user_by( 'ID', get_post_meta( $post_id, '_bptk_member_reported', true ) );

				$substantiated = ' | Upheld <span class="bptk-reports-count">(' . bptk_substantiated_reports_about_user( $user->ID ) . ')</span>';

				echo '<div>' . bp_core_fetch_avatar( array(
						'item_id' => get_post_meta( $post_id, '_bptk_member_reported', true )
					) ) . '<div><span>' . $user->display_name . '</span><span>Reports <span class="bptk-reports-count">(' . bptk_reports_about_user( $user->ID ) . ')</span>' . $substantiated . '</span></div></div>';
				break;

Security Fix

--- admin/class-bp-toolkit-reports-screen.php
+++ admin/class-bp-toolkit-reports-screen.php
@@ -118,7 +118,7 @@
 		switch ( $column_name ) {
 
 			case 'content' :
-				echo get_the_excerpt( $post_id );
+				echo esc_html( get_the_excerpt( $post_id ) );
 				break;
 
 			case 'type' :
@@ -141,7 +141,7 @@
 
 			case 'reporter' :
 				$user = get_user_by( 'ID', get_post_meta( $post_id, '_bptk_reported_by', true ) );
-				echo '<a href="' . get_edit_post_link( $post_id ) . '"><div>' . bp_core_fetch_avatar( array( 'item_id' => $user->ID ) ) . '<div><span>' . $user->display_name . '</span><span>' . get_the_date( "F j, Y \\a\\t g:i a",
+				echo '<a href="' . esc_url( get_edit_post_link( $post_id ) ) . '"><div>' . bp_core_fetch_avatar( array( 'item_id' => $user->ID ) ) . '<div><span>' . esc_html( $user->display_name ) . '</span><span>' . get_the_date( "F j, Y \\a\\t g:i a",
 						$post_id ) . '</span></div></div></a>';
 				break;
 
@@ -153,7 +153,10 @@
 
 				echo '<div>' . bp_core_fetch_avatar( array(
 						'item_id' => get_post_meta( $post_id, '_bptk_member_reported', true )
-					) ) . '<div><span>' . $user->display_name . '</span><span>Reports <span class="bptk-reports-count">(' . bptk_reports_about_user( $user->ID ) . ')</span>' . $substantiated . '</span></div></div>';
+					) ) . '<div><span>' . esc_html( $user->display_name ) . '</span><span>Reports <span class="bptk-reports-count">(' . (int) bptk_reports_about_user( $user->ID ) . ')</span>' . esc_html( $substantiated ) . '</span></div></div>';
+				if ( $reported_link = get_post_meta( $post_id, '_bptk_reported_item_link', true ) ) {
+					echo '<br><a href="' . esc_url( $reported_link ) . '">' . esc_html__( 'View Reported Item', 'bp-toolkit' ) . '</a>';
+				}
 				break;

Exploit Outline

1. Login to the WordPress site as a Subscriber-level user. 2. Navigate to a BuddyPress member profile or activity item where a 'Report' button is available. 3. Extract the required WordPress nonce from the page, usually found in a localized JavaScript object (e.g., window.bptk_ajax.nonce). 4. Send a POST request to /wp-admin/admin-ajax.php with the following parameters: action=bptk_submit_report, nonce=[NONCE], and the 'link' parameter set to an XSS payload such as javascript:alert(document.domain) or a link containing <script> tags. 5. The payload is stored as metadata (e.g., _bptk_reported_item_link) for a new post of type 'report'. 6. The XSS triggers when an administrator navigates to the 'Reports' list in the admin dashboard (wp-admin/edit.php?post_type=report) and the stored link is rendered without escaping.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.