Moodle 5.2.3 - High - Remote - IDOR - core_grades_get_feedback skips separate-groups checks
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · moodle-grades-feedback-idor
Moodle LMS 5.2.3 - Moodle
Class: IDOR Reach: Remote
core_grades_get_feedback still only calls require_capability('gradereport/grader:view'). Other grade web services gained groups_user_groups_visible / groups_group_visible after MSA-26-0016 / MDL-88667. This AJAX service did not. The JSON comes back with feedbacktext, fullname, picture, and the first identity field.
A non-editing teacher in Separate groups, without accessallgroups, POSTs another student's userid and reads that group's feedback.
| ID | no CVE yet |
| CWE | CWE-863 / CWE-200 |
| CVSS | Medium: 4.3 CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N |
| Product | Moodle |
| Affected | 5.2.3 tag v5.2.3 (344232c15336c71b80f9aca8359ce0e0a9f3d116) |
| Auth | non-editing teacher with gradereport/grader:view, without moodle/site:accessallgroups. Course in Separate groups. |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
Writer: a non-editing teacher enrolled in a Separate-groups course, in Group A only, with grader view and without access-all-groups. They pick another enrolled student's userid (sequential ints) and call the AJAX service. Not RCE. Not LPE. Not a score write.
| Privilege | Method / sink | Recon / integrity / availability | Lab-proven or inferred |
|---|---|---|---|
| Read another group's grade feedback | POST /lib/ajax/service.php?sesskey= core_grades_get_feedback {courseid, userid, itemid} | Recon | Lab-proven. HTTP 200, error=false, feedbacktext contains MOODLE-GRADES-FEEDBACK-IDOR-WITNESS |
| Read that student's identity field and display name | Same response additionalfield / fullname / picture | Recon | Lab-proven. [email protected], fullname=Student Bravo |
Walk sequential userid values in the same course | Same method, guessed ids | Recon | Inferred. Lab used the seeded Group B id |
| List Group B through the sibling selector | core_grades_get_gradable_users {courseid, groupid=B} | n/a | Lab-proven closed. cannotaccessgroup |
| Student calls the same feedback service | student role, same args | n/a | Lab-proven closed. nopermissions |
| Same-group feedback (Group A student) | teacher call for Student Alpha | Recon (intended) | Lab-proven allowed. HTTP 200, error=false |
| PHP RCE / OS LPE / grade write | n/a | n/a | Closed. This leftover is a read of feedback and profile fields |
Default Moodle without Separate groups, or a teacher who already has moodle/site:accessallgroups, is not this leftover. Editing teachers with that cap already see every group.
Same pin leftovers: Blackboard img file read, enrol_database SQLi, hidden grade items.
I pinned moodle/moodle v5.2.3 after MSA-26-0042/0043. MSA-26-0016 closed group checks on other grade web services. get_feedback.php was not in that set. The grader UI was hardened. This AJAX service was not.
Wrong turns already recorded: treating the sibling get_gradable_users miss as the whole story (that one already throws cannotaccessgroup); calling this a score write because the payload names itemid (the service is a read); student-as-attacker (nopermissions). A reverse shell. Theatre.
Live Moodle v5.2.3 on php:8.3-cli + MariaDB 11.4. Seed builds a Separate-groups course, teacher in Group A only, student B in Group B only, CAP_PREVENT on moodle/site:accessallgroups, and a manual grade item whose feedback is the witness. how=live.
Compose bind-mounts ../../vendors/moodle/src as /opt/moodle-pin (path is relative to lab/docker-compose.yml). Clone moodle/moodle tag v5.2.3 to that path, or point the volume at your checkout. The Moodle tree is not in this pack.
python3 moodle-grades-feedback-idor-Abraxas-Labs.py
or
cd lab
./run.sh
Target only http://127.0.0.1:19580. Compose project moodle-grades-feedback-idor. Stack tears down after the run.
SUCCESS moodle-grades-feedback-idor how=live pin=v5.2.3 teacher-role=teacher accessallgroups=no groupmode=1 feedback-has-witness=yes [email protected] sibling-group-b=cannotaccessgroup student-call=nopermissions MOODLE-GRADES-FEEDBACK-IDOR-WITNESS
After validate_context / require_capability, load the course and the target user, then refuse unless groups_user_groups_visible($course, $userid) and the target is enrolled. Same shape as gradereport_user and core_grades_get_gradable_users after MDL-88667.
get_feedback.phpget_gradable_users.phpGNU Affero GPL v3.0. See LICENSE. Loopback lab only. No warranty.