CVE-2026-58335
Patch Available.
This Vulnerability has been fixed in the Never-Ending Support (NES) version offered by HeroDevs.
Overview
Moodle is an open source learning management system written in PHP. Schools, universities, and companies use it to run online courses. IOMAD is a multi-tenant distribution of Moodle that lets one installation serve several companies or organizations; it is built on Moodle core and shares the affected code. Courses can use separate groups mode, in which users without moodle/site:accessallgroups should only see members of their own groups. By default, only editing teachers and managers have that capability, so students and non-editing teachers are limited to their groups.
An Improper Access Control vulnerability (CVE-2026-58335) has been identified in the gradebook web services of Moodle and IOMAD, which allows a user in a separate-groups course to list users from groups they do not belong to through core_grades_get_enrolled_users_for_selector and core_grades_get_gradable_users. Moodle tracks this issue as MSA-26-0016 (MDL-88667).
Per MITRE CWE-284: The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.
This issue affects all versions of Moodle before 4.5.12, versions 5.0.0 through 5.0.7, versions 5.1.0 through 5.1.4, and version 5.2.0, including End-of-Life release lines such as Moodle 4.3. IOMAD releases built on these Moodle versions contain the same code and are affected as well.
Details
Module Info
- Product: Moodle, IOMAD
- Affected packages:
moodle/moodle(grade/lib.php,grade/classes/external) - Affected versions: <4.5.12, >=5.0.0 <5.0.8, >=5.1.0 <5.1.5, >=5.2.0 <5.2.1
- GitHub repository: https://github.com/moodle/moodle (Moodle), https://github.com/iomad/iomad (IOMAD)
- Published packages: https://packagist.org/packages/moodle/moodle
- Package manager: Composer
- Fixed in:
- OSS Moodle 4.5.12, 5.0.8, 5.1.5, 5.2.1
- NES for Moodle v4.3.13
- NES for IOMAD v4.3.4
Vulnerability Info
This Low-severity vulnerability is found in the moodle/moodle package in all versions before 4.5.12, 5.0.0 through 5.0.7, 5.1.0 through 5.1.4, and 5.2.0 of Moodle, and in IOMAD releases built on those versions. Moodle rates it Minor.
The gradebook's user selectors fetch their lists through two web services, core_grades_get_enrolled_users_for_selector and core_grades_get_gradable_users. Both take a course ID and an optional group ID and require only moodle/course:viewparticipants, which students and teachers have by default. Neither checked that the caller could see the requested group. The selector service passed the group straight to graded_users_iterator:
require_capability('moodle/course:viewparticipants', $coursecontext);
$course = $DB->get_record('course', ['id' => $params['courseid']]);
// Create a graded_users_iterator because it will properly check the groups etc.
$defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
$showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
$showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
$gui = new \graded_users_iterator($course, null, $params['groupid']);
$gui->require_active_enrolment($showonlyactiveenrol);
$gui->init();When no group was given, graded_users_iterator::init() in grade/lib.php did not apply the course's group mode at all. It filtered by group only when a group ID was passed:
if ($this->groupid) {
$groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id";
$groupwheresql = "AND gm.groupid = :groupid";
// $params contents: gradebookroles
$params['groupid'] = $this->groupid;
} else {
$groupsql = "";
$groupwheresql = "";
}In a course that uses separate groups, a student or non-editing teacher could call either service with no group to get every gradable user in the course, or with the ID of another group to get that group's members. The responses include names and profile picture URLs, and the selector service also returns the identity fields the caller is allowed to see. Because graded_users_iterator is also used elsewhere in the gradebook, the missing default could also affect other places that list users with their grades. Courses that do not use separate groups are not affected, because their participants are visible to each other anyway.
The fix makes both services refuse a group the caller cannot see:
if ($params['groupid'] && !groups_group_visible($params['groupid'], $course)) {
throw new \moodle_exception('cannotaccessgroup', 'core_grades');
}graded_users_iterator::init() now passes the group to get_enrolled_sql(). When no group is given in a separate-groups course and the user lacks moodle/site:accessallgroups, it limits the results to members of the user's own groups, or returns no one if the user is not in any group.
Mitigation
Moodle publishes security fixes only for its supported release lines. Earlier release lines, including Moodle 4.3 and the IOMAD releases built on it, are End-of-Life and will not receive any public updates to address this issue.
Users of the affected components should apply one of the following mitigations:
- Upgrade to Moodle 4.5.12, 5.0.8, 5.1.5, 5.2.1, or later, or to an IOMAD release built on one of those versions.
- Leverage a commercial support partner like HeroDevs for post-EOL security support.
Credits
- Paul Holden from Moodle HQ (finder)