CVE-2025-62438
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. Plugins can control who may view a user's profile by implementing a control_view_profile callback, which can prevent access or force it to be allowed. Local plugins use it, for example, to hide some users' profiles from other users on the site.
An Improper Access Control vulnerability (CVE-2025-62438) has been identified in the user details code of Moodle and IOMAD, which allows a user to retrieve profile details through web services for users whose profiles a plugin's control_view_profile callback is meant to hide. Only sites with a plugin that implements this callback are affected. Moodle tracks this issue as MSA-25-0040 (MDL-63395).
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.1.20, versions 4.2.0 through 4.4.9, versions 4.5.0 through 4.5.5, and versions 5.0.0 through 5.0.1, 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(user/lib.php) - Affected versions: <4.1.20, >=4.2.0 <4.4.10, >=4.5.0 <4.5.6, >=5.0.0 <5.0.2
- 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.1.20, 4.4.10, 4.5.6, 5.0.2
- 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.1.20, 4.2.0 through 4.4.9, 4.5.0 through 4.5.5, and 5.0.0 through 5.0.1 of Moodle, and in IOMAD releases built on those versions. Moodle rates it Minor.
user_can_view_profile(), which guards the profile pages, runs the plugin callbacks and denies access when any plugin returns VIEWPROFILE_PREVENT:
// Use callbacks so that (primarily) local plugins can prevent or allow profile access.
$forceallow = false;
$plugintypes = get_plugins_with_function('control_view_profile');
foreach ($plugintypes as $plugins) {
foreach ($plugins as $pluginfunction) {
$result = $pluginfunction($user, $course, $usercontext);
switch ($result) {
case core_user::VIEWPROFILE_PREVENT:
return false;
}
}
}Web services do not use that function. They build user records with user_get_user_details(), which is called by functions such as core_user_get_users_by_field, core_user_get_course_user_profiles, and core_enrol_get_enrolled_users. user_get_user_details() decided whether to return a user only from capabilities:
if (!$currentuser && !$canviewdetailscap && !has_coursecontact_role($user->id)) {
// Skip this user details.
return null;
}It never ran the control_view_profile callbacks. By default, moodle/user:viewdetails is granted in course context to students, teachers, and managers, so on a site where a plugin uses the callback to hide certain profiles, an ordinary course participant who can call these web services, including through the Moodle mobile app, can still receive the hidden users' details, such as names, profile fields, and other data the caller's capabilities allow.
Sites that do not have a plugin implementing control_view_profile are not exposed, since without a callback the capability checks are the whole policy.
The fix moves the callback loop into a new function, user_process_profile_callbacks(), used by both user_can_view_profile() and user_get_user_details(). user_get_user_details() now runs the callbacks for any user other than the caller before the capability checks, and returns nothing when a plugin prevents access:
// Callback check for plugins to allow or prevent access.
$forceallow = true;
$currentuser = ($user->id == $USER->id);
$isadmin = is_siteadmin($USER);
if (!$currentuser) {
$forceallow = false;
$callbackresult = user_process_profile_callbacks($user, $course);
if ($callbackresult === core_user::VIEWPROFILE_PREVENT) {
return null; // Access denied.
} else if ($callbackresult === core_user::VIEWPROFILE_FORCE_ALLOW) {
$forceallow = true;
}
}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.1.20, 4.4.10, 4.5.6, 5.0.2, 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
- Albert Gasset (finder)