CVE-2025-62398
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. Moodle's multi-factor authentication (MFA) tool, tool_mfa, has been part of core since Moodle 4.3. After a user enters a password, it holds the session at its own authentication page until the user has passed enough factors.
An Authentication Bypass vulnerability (CVE-2025-62398) has been identified in the multi-factor authentication tool of Moodle and IOMAD, which allows an attacker who knows a user's password to skip the second factor and call Moodle's AJAX web services as that user. Only sites with MFA enabled are affected. Moodle tracks this issue as MSA-25-0047 (MDL-86334).
Per MITRE CWE-288: The product requires authentication, but the product has an alternate path or channel that does not require authentication.
This issue affects Moodle versions 4.3.0 through 4.4.10, versions 4.5.0 through 4.5.6, and versions 5.0.0 through 5.0.2, 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(admin/tool/mfa,lib/setup.php) - Affected versions: >=4.3.0 <4.4.11, >=4.5.0 <4.5.7, >=5.0.0 <5.0.3
- 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.4.11, 4.5.7, 5.0.3
- NES for Moodle v4.3.13
- NES for IOMAD v4.3.4
Vulnerability Info
This Medium-severity vulnerability is found in the moodle/moodle package in versions 4.3.0 through 4.4.10, 4.5.0 through 4.5.6, and 5.0.0 through 5.0.2 of Moodle, and in IOMAD releases built on those versions. Moodle's advisory lists 4.4 and later; the same code is present in 4.3, the first release with the MFA tool in core. Moodle rates it Serious.
On each request from a logged-in user who has not finished MFA, tool_mfa calls manager::require_auth(), which asks should_require_mfa() whether to send the user to the MFA page. Some pages have to work before MFA is complete, such as site policy pages, the MFA guide, logout, and pages that individual factors register. For those, the function returns NO_REDIRECT, and require_auth() then sets $SESSION->mfa_pending = true so that such a page can make its own AJAX calls. should_require_mfa() trusted that flag for every AJAX or web service request in the session, whatever the request did:
// WS/AJAX check.
if (WS_SERVER || AJAX_SCRIPT) {
if (isset($SESSION->mfa_pending) && !empty($SESSION->mfa_pending)) {
// Allow AJAX and WS, but never from auth.php.
return self::NO_REDIRECT;
}
return self::REDIRECT_EXCEPTION;
}An attacker who has a user's username and password can log in, open a page that MFA lets through, such as /admin/tool/mfa/guide.php, to set mfa_pending, and then send requests to lib/ajax/service.php, which runs as an AJAX script. MFA no longer stops those requests, so the attacker can call the AJAX-enabled external functions available to the victim's account and read or change data that the second factor was meant to protect. A valid password is still required, and sites without MFA enabled are not exposed.
The fix removes the exception, so an AJAX or web service request from a session that has not passed MFA always gets REDIRECT_EXCEPTION, which require_auth() turns into an error:
// WS/AJAX check.
// Prevents any potential bypassing of multi-factor authentication.
if (WS_SERVER || AJAX_SCRIPT) {
return self::REDIRECT_EXCEPTION;
}On the 4.4 line, and in 4.3, lib/setup.php ran each plugin's after_config callback, the hook from which tool_mfa enforces MFA, inside a try/catch block that discarded exceptions. For those lines the fix also removes that wrapper, so the MFA exception stops the request instead of being swallowed:
$pluginswithfunction = get_plugins_with_function('after_config', 'lib.php');
foreach ($pluginswithfunction as $plugins) {
foreach ($plugins as $function) {
try {
$function();
} catch (Throwable $e) {
debugging("Exception calling '$function'", DEBUG_DEVELOPER, $e->getTrace());
}
}
}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.4.11, 4.5.7, 5.0.3, 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
- Petr Skoda (finder)