CVE-2025-67855
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. The policies tool (tool_policy) shows site policies to users and records their consent. Several of its pages, including view.php and viewall.php, accept a returnurl parameter so the user can be sent back to where they came from. That parameter is cleaned with PARAM_LOCALURL, the Moodle parameter type for URLs that must point to the local site.
A Cross-Site Scripting (XSS) vulnerability (CVE-2025-67855) has been identified in the PARAM_LOCALURL cleaning used by the policy tool of Moodle and IOMAD, which allows an attacker to craft a link whose return URL carries script that runs in the victim's browser on the Moodle site. Moodle tracks this issue as MSA-25-0059 (MDL-86544).
Per OWASP: Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted websites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end user.
This issue affects all versions of Moodle before 4.1.22, versions 4.2.0 through 4.4.11, versions 4.5.0 through 4.5.7, versions 5.0.0 through 5.0.3, and version 5.1.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(lib/classes/param.php,lib/moodlelib.php,admin/tool/policy) - Affected versions: <4.1.22, >=4.2.0 <4.4.12, >=4.5.0 <4.5.8, >=5.0.0 <5.0.4, >=5.1.0 <5.1.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.1.22, 4.4.12, 4.5.8, 5.0.4, 5.1.1
- 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 all versions before 4.1.22, 4.2.0 through 4.4.11, 4.5.0 through 4.5.7, 5.0.0 through 5.0.3, and 5.1.0 of Moodle, and in IOMAD releases built on those versions. Moodle rates it Serious.
PARAM_LOCALURL first cleans the value as a PARAM_URL, then accepts it if it equals the site's wwwroot, starts with /, or starts with the wwwroot. Anything else is treated as a relative URL and checked once more for a javascript: scheme. In Moodle 4.4 and later this logic is in \core\param::clean_param_value_localurl() in lib/classes/param.php; in older release lines the same check is in clean_param() in lib/moodlelib.php. The relative-URL branch looked like this:
} else {
// Relative - let's make sure there are no tricks.
if (validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?') &&
!preg_match('/javascript(?:.*\/{2,})?:/i', rawurldecode($param))) {
// Valid relative local URL.
} else {
$param = '';
}
}The pattern only recognised javascript when a colon followed it directly, or followed a run of characters ending in two or more slashes. A value can be built that passes the URL syntax check and that this pattern does not see as a javascript: URL, so PARAM_LOCALURL returned it as a valid relative URL. Code that trusts PARAM_LOCALURL to produce a safe link target, as the policy tool does with returnurl, then puts that value into the page.
An attacker who gets a user to open a crafted policy tool link can have script run in that user's browser in the context of the Moodle site when the return link is used. The script runs with the victim's session, so it can read what the victim can see and act as the victim; against an administrator, that can include changing site configuration. No account is needed to send the link, but the victim has to open it and interact with the page.
The fix replaces the pattern with a stricter one that matches javascript as a whole word followed, anywhere later in the decoded value, by a colon or a slash:
} else {
// Relative - let's make sure there are no tricks.
if (
validateUrlSyntax('/' . $param, 's-u-P-a-p-f+q?r?') &&
!preg_match('/\bjavascript\b[\s\S]*?(?:\:|\/)/i', rawurldecode($param))
) {
// Valid relative local URL.
} else {
$param = '';
}
}The stricter pattern changes behaviour for some legitimate values, in every patched release. A relative local URL that merely contains the whole word javascript before a : or /, such as docs/javascript/index.html or course/view.php?t=javascript/intro, is now cleaned to an empty string. The effect is narrow: root-relative URLs (starting with /) and absolute URLs under the site's wwwroot are accepted before the check runs, and the match is whole-word only, so docs/javascripts/index.html still passes. Sites that pass such relative paths through PARAM_LOCALURL should make them root-relative or use a different parameter type.
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.22, 4.4.12, 4.5.8, 5.0.4, 5.1.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
- Nicecatch2000 (finder)