
Moodle 5.2.3 - High - Remote - Path - Blackboard img src copies files outside the import tree
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · moodle-blackboard-img
Moodle LMS 5.2.3 - Moodle
Class: File read Reach: Remote
qformat_blackboard_six_base::text_field joins img src onto $this->filebase with no PARAM_PATH. store_file_for_text_field then copies that path into the user draft filearea. Same-family get_filecontent already jails with clean_param($path, PARAM_PATH). MSA-26-0012 jailed database-activity import with PARAM_FILE. Blackboard img src was not in that set.
A teacher who can import a Blackboard zip plants <img src="https://raw.githubusercontent.com/html/config.php">. The draft "image" is config.php. Students cannot import.
| ID | no CVE yet |
| CWE | CWE-22 / CWE-73 |
| CVSS | Medium: 6.5 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N |
| Product | Moodle |
| Affected | 5.2.3 tag v5.2.3 (344232c15336c71b80f9aca8359ce0e0a9f3d116) |
| Auth | teacher (question bank / lesson Blackboard import) |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Writer: a teacher who can import a Blackboard zip into the question bank (or lesson). Typical prize is config.php (DB password) as the PHP user. Not RCE unless they chain a second leftover. Not LPE.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
Read a file the PHP user can open, via ../ in img src | text_field joins $this->filebase . '/' . $path; store_file_for_text_field copies into the draft area | Recon (file bytes) | Lab-proven. Draft bytes match /var/www/html/config.php and contain MOODLE-BLACKBOARD-IMG-WITNESS |
| Download that draft as the "image" | draft itemid + rewritten @@PLUGINFILE@@ filename | Recon | Lab-proven. GET /draft returned 298 bytes, sha256 equal to config.php |
| Anyone who can later open the imported question | Same draft / pluginfile | Recon | Inferred. Lab stopped at the teacher draft match |
| Student import | same zip, role=student | n/a | Lab-proven closed. HTTP 403 teacher-only |
Same path through clean_param($path, PARAM_PATH) | gadget jail=1 | n/a | Lab-proven closed. No match, no witness, path stays under filebase |
| PHP RCE / OS LPE | n/a | n/a | Closed. This leftover copies file bytes into a draft. It does not execute them |
Default Moodle without Blackboard import is not this bug. MSA-22-0016 only cleaned baseurl.
Same pin leftovers: enrol_database SQLi, grades feedback IDOR, hidden grade items.
I pinned moodle/moodle v5.2.3 after MSA-26-0042/0043. MSA-26-0012 jailed database-activity import. I sat on Blackboard next because get_filecontent() already knew about PARAM_PATH and text_field() still did a raw join.
Wrong turns already recorded: treating student import as the leak (it is 403); calling this RCE because the copied file is PHP (the draft stores bytes, it does not include them); assuming MSA-22-0016 had finished the job (baseurl only). A reverse shell. Theatre.
Faithful gadget of the pin text_field / store_file_for_text_field, not a full Moodle PHP app. how=gadget.
python3 moodle-blackboard-img-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19560. Compose project moodle-blackboard-img. Stack tears down after the run.
SUCCESS moodle-blackboard-img how=gadget pin=v5.2.3 teacher-import=yes draft-match-config=yes student=403 param-path-jail=yes MOODLE-BLACKBOARD-IMG-WITNESS
clean_param($path, PARAM_PATH) on img src, then realpath must stay under realpath($this->filebase). Same shape as get_filecontent().
formatbase.php (text_field, store_file_for_text_field)format.php (get_filecontent already uses PARAM_PATH)GNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.