CVE-2025-49514
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 makes outgoing HTTP requests through its curl wrapper class in lib/filelib.php. Before each request, the wrapper checks the target against the cURL blocked hosts list and cURL allowed ports list settings, which by default block loopback, private network, and cloud metadata addresses.
A Server-Side Request Forgery (SSRF) vulnerability (CVE-2025-49514) has been identified in the outgoing request handling of Moodle and IOMAD, which allows an attacker who controls the DNS records of a domain to get past the cURL blocked hosts and allowed ports settings and make Moodle send requests to internal addresses. Moodle tracks this issue as MSA-25-0032 (MDL-83762).
Per OWASP: The target application may have functionality for importing data from a URL, publishing data to a URL or otherwise reading data from a URL that can be tampered with. The attacker modifies the calls to this functionality by supplying a completely different URL or by manipulating how URLs are built (path traversal etc.). When the manipulated request goes to the server, the server-side code picks up the manipulated URL and tries to read data to the manipulated URL.
This issue affects all versions of Moodle before 4.1.19, versions 4.2.0 through 4.4.8, versions 4.5.0 through 4.5.4, and version 5.0.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/files/curl_security_helper.php,lib/filelib.php) - Affected versions: <4.1.19, >=4.2.0 <4.4.9, >=4.5.0 <4.5.5, >=5.0.0 <5.0.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.19, 4.4.9, 4.5.5, 5.0.1
- NES for Moodle v4.3.13
- NES for IOMAD v4.3.4
Vulnerability Info
This High-severity vulnerability is found in the moodle/moodle package in all versions before 4.1.19, 4.2.0 through 4.4.8, 4.5.0 through 4.5.4, and 5.0.0 of Moodle, and in IOMAD releases built on those versions. Moodle rates it Serious.
The curl class asks \core\files\curl_security_helper::url_is_blocked() whether a URL may be fetched. When the host is a domain name and the blocked hosts list contains IP rules, host_is_blocked() resolves the name with gethostbynamel() and checks each returned address against the list. The addresses it checked were then thrown away:
// If any of the returned IPs are in the blocklist, block the request.
foreach ($hostips as $hostip) {
if ($this->address_explicitly_blocked($hostip)) {
return true;
}
}After the check passed, curl::request() handed only the URL to cURL, and cURL did its own DNS lookup when it connected. The name was resolved twice, and nothing tied the second answer to the first. An attacker who controls a domain's DNS server can answer the first lookup with a public address that passes the check and the second with an address on the blocked list, such as 127.0.0.1 or the cloud metadata address 169.254.169.254. This is DNS rebinding, and it lets the request reach a host the site administrator meant to block.
Moodle fetches remote URLs in many places, and some of them take the URL from a user, so a user who can submit such a URL may be able to make the server send requests into its internal network or to a cloud metadata service. The result can be returned to the attacker or only observed indirectly, depending on the feature used. URLs that name an IP address directly were already checked correctly, because cURL does no lookup for them; the gap affects only URLs that use a domain name.
The fix makes the security helper remember the addresses and port that passed the check, and get_resolve_info() returns them in the host:port:address format that cURL's CURLOPT_RESOLVE option expects. curl::request() now passes that list to cURL through CURLOPT_RESOLVE, so cURL connects only to the addresses that were validated instead of looking the name up again:
// Force cURL to only resolve the URL from IP/port combinations that were validated by the security helper.
// This prevents re-fetching DNS data on subsequent requests, which could return un-validated hosts/ports.
$this->setopt(['CURLOPT_RESOLVE' => $this->curlresolveinfo]);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.19, 4.4.9, 4.5.5, 5.0.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
- Rekter0 (finder)
- Holme (finder)
- 0x123456789 (finder)
- TaiYou (finder)
- Vladislav Gladkiy of Positive Technologies (finder)