Moodle: moodle: data exposure of user identifiers in urls
Description
A flaw was found in moodle. During anonymous assignment submissions, user identifiers were inadvertently exposed in URLs. This data exposure allows unauthorized viewers to see internal user IDs, compromising the intended anonymity and potentially leading to information disclosure.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Anonymous assignment submissions in Moodle expose internal user IDs in URLs, breaking anonymity and risking information disclosure.
Overview
The vulnerability lies in Moodle's assignment module (mod_assign). When a teacher or system processes anonymous (blind-marked) submissions, the application constructs URLs that contain the numeric userid parameter rather than the anonymous blindid identifier [2]. This occurs because several actions—such as addattempt, view_grant_extension, and view_remove_submission_confirm—directly used the user's real ID in the URL even when blind marking was enabled [3][4].
Root
Cause and Exploitation
The core flaw is that the logic for selecting which identifier to include in URLs did not consistently check whether the assignment was in blind-marking mode. For example, in the addattempt action the code would accept userid via required_param() without first attempting to use the blindid parameter [3]. Similarly, the view_remove_submission_confirm method defaulted to $USER->id without consulting the blind ID [4]. An attacker needs only to view the URL (or intercept network traffic) while a teacher or participant performs an action on an anonymous submission. The attacker does not require any special privileges beyond being able to see the URL, which may be logged in server logs, browser history, or shared inadvertently [1][2].
Impact
Successful exploitation allows an unauthorized viewer to map internal user IDs to specific submissions, completely defeating the anonymity intended for blind marking [2]. This can lead to information disclosure—for instance, learning which student submitted which work, potentially revealing personal data or enabling targeted harassment [1]. The CVSS score or vector is not yet provided by NVD, but the confidentiality impact is clearly partial [2].
Mitigation
Moodle has addressed the issue by modifying three key code locations in commit c6cb8d9 [3] and a follow-up commit ac30e7e [4]. The patches replace userid parameters with blindid in blind-marking contexts, and only fall back to real user IDs when no blind identifier is available. Administrators should update Moodle to a version that includes these fixes. As of the publication date (February 2026), the vulnerability is not listed on CISA's Known Exploited Vulnerabilities (KEV) catalog.
AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
moodle/moodlePackagist | < 4.1.22 | 4.1.22 |
moodle/moodlePackagist | >= 4.4.0-beta, < 4.4.12 | 4.4.12 |
moodle/moodlePackagist | >= 4.5.0-beta, < 4.5.8 | 4.5.8 |
moodle/moodlePackagist | >= 5.0.0-beta, < 5.0.4 | 5.0.4 |
moodle/moodlePackagist | >= 5.1.0-beta, < 5.1.1 | 5.1.1 |
Affected products
2Patches
2ac30e7e19357MDL-82808 assign: Use blindid during submission removal in blind marking
1 file changed · +19 −15
public/mod/assign/locallib.php+19 −15 modified@@ -3216,9 +3216,7 @@ protected function view_grant_extension($mform) { if (!$users) { if ($this->is_blind_marking()) { $blindid = optional_param('blindid', 0, PARAM_INT); - if ($blindid) { - $users = $this->get_user_id_for_uniqueid($blindid); - } + $users = $this->get_user_id_for_uniqueid($blindid); } // We need users, if not found by blindid. if (!$users) { @@ -4441,13 +4439,25 @@ protected function view_remove_submission_confirm() { $userid = optional_param('userid', 0, PARAM_INT); $blindid = optional_param('blindid', 0, PARAM_INT); - if ($this->is_blind_marking() && !$userid && $blindid) { - $userid = $this->get_user_id_for_uniqueid($blindid); - } + // Construct the base URL parameters for the confirm and cancel actions. + $urlparams = [ + 'id' => $this->get_course_module()->id, + 'action' => 'removesubmission', + 'sesskey' => sesskey(), + ]; - // If no userid specified, default to current user. - if (!$userid) { + // Determine the correct user ID. + if ($userid) { + // Real user ID was explicitly provided. + $urlparams['userid'] = $userid; + } elseif ($this->is_blind_marking() && $blindid) { + // Blind marking is in use. Resolve anonymized (blind) ID to the real user ID to obtain the user object later. + $userid = $this->get_user_id_for_uniqueid($blindid); + $urlparams['blindid'] = $blindid; + } else { + // Default to the currently logged-in user (e.g. user deleting their own submission scenario). $userid = $USER->id; + $urlparams['userid'] = $userid; } if (!$this->can_edit_submission($userid, $USER->id)) { @@ -4462,10 +4472,6 @@ protected function view_remove_submission_confirm() { $this->get_course_module()->id); $o .= $this->get_renderer()->render($header); - $urlparams = array('id' => $this->get_course_module()->id, - 'action' => 'removesubmission', - 'userid' => $userid, - 'sesskey' => sesskey()); $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams); $urlparams = array('id' => $this->get_course_module()->id, @@ -8502,9 +8508,7 @@ protected function process_remove_submission($userid = 0) { if (!$userid) { if ($this->is_blind_marking()) { $blindid = optional_param('blindid', 0, PARAM_INT); - if ($blindid) { - $userid = $this->get_user_id_for_uniqueid($blindid); - } + $userid = $this->get_user_id_for_uniqueid($blindid); } // Userid is required, if not found by blindid.
c6cb8d971257MDL-82808 mod_assign: replace userid with blindid in anonymous marking
2 files changed · +114 −16
public/mod/assign/gradingtable.php+16 −6 modified@@ -1162,12 +1162,22 @@ public function col_status(stdClass $row) { ); $caneditsubmission = $this->assignment->can_edit_submission($row->id, $USER->id); - $baseactionurl = new moodle_url('/mod/assign/view.php', [ - 'id' => $this->assignment->get_course_module()->id, - 'userid' => $row->id, - 'sesskey' => sesskey(), - 'page' => $this->currpage, - ]); + $urlparams = [ + 'id' => $this->assignment->get_course_module()->id, + 'sesskey' => sesskey(), + 'page' => $this->currpage, + ]; + + if ($this->assignment->is_blind_marking()) { + if (empty($row->recordid)) { + $row->recordid = $this->assignment->get_uniqueid_for_user($row->userid); + } + $urlparams['blindid'] = $row->recordid; + } else { + $urlparams['userid'] = $row->userid; + } + + $baseactionurl = new moodle_url('/mod/assign/view.php', $urlparams); $menu = new action_menu(); $menu->set_owner_selector('.gradingtable-actionmenu');
public/mod/assign/locallib.php+98 −10 modified@@ -556,7 +556,16 @@ public function view($action='', $args = array()) { $nextpageparams['action'] = 'view'; } } else if ($action == 'addattempt') { - $this->process_add_attempt(required_param('userid', PARAM_INT)); + $userid = false; + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + $userid = $this->get_user_id_for_uniqueid($blindid); + } + // Userid is required, if not found by blindid. + if (!$userid) { + $userid = required_param('userid', PARAM_INT); + } + $this->process_add_attempt($userid); $action = 'redirect'; $nextpageparams['action'] = 'grading'; } else if ($action == 'reverttodraft') { @@ -3205,7 +3214,16 @@ protected function view_grant_extension($mform) { $users = optional_param('userid', 0, PARAM_INT); if (!$users) { - $users = required_param('selectedusers', PARAM_SEQUENCE); + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + if ($blindid) { + $users = $this->get_user_id_for_uniqueid($blindid); + } + } + // We need users, if not found by blindid. + if (!$users) { + $users = required_param('selectedusers', PARAM_SEQUENCE); + } } $userlist = explode(',', $users); @@ -4420,7 +4438,17 @@ protected function view_single_grade_page($mform) { protected function view_remove_submission_confirm() { global $USER; - $userid = optional_param('userid', $USER->id, PARAM_INT); + $userid = optional_param('userid', 0, PARAM_INT); + $blindid = optional_param('blindid', 0, PARAM_INT); + + if ($this->is_blind_marking() && !$userid && $blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + + // If no userid specified, default to current user. + if (!$userid) { + $userid = $USER->id; + } if (!$this->can_edit_submission($userid, $USER->id)) { throw new \moodle_exception('nopermission'); @@ -4750,7 +4778,7 @@ protected function view_grader() { $userid = optional_param('userid', 0, PARAM_INT); $blindid = optional_param('blindid', 0, PARAM_INT); - if (!$userid && $blindid) { + if ($this->is_blind_marking() && !$userid && $blindid) { $userid = $this->get_user_id_for_uniqueid($blindid); } @@ -4887,7 +4915,17 @@ protected function view_edit_submission_page($mform, $notices) { $o = ''; require_once($CFG->dirroot . '/mod/assign/submission_form.php'); // Need submit permission to submit an assignment. - $userid = optional_param('userid', $USER->id, PARAM_INT); + $userid = optional_param('userid', 0, PARAM_INT); + $blindid = optional_param('blindid', 0, PARAM_INT); + + if ($this->is_blind_marking() && !$userid && $blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + // If no userid specified, default to current user. + if (!$userid) { + $userid = $USER->id; + } + $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST); $timelimitenabled = get_config('assign', 'enabletimelimit'); @@ -7015,7 +7053,17 @@ protected function process_submit_other_for_grading($mform, &$notices) { require_sesskey(); - $userid = optional_param('userid', $USER->id, PARAM_INT); + $userid = optional_param('userid', 0, PARAM_INT); + $blindid = optional_param('blindid', 0, PARAM_INT); + + if ($this->is_blind_marking() && !$userid && $blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + + // If no userid specified, default to current user. + if (!$userid) { + $userid = $USER->id; + } if (!$this->submissions_open($userid)) { $notices[] = get_string('submissionsclosed', 'assign'); @@ -8452,7 +8500,17 @@ protected function process_remove_submission($userid = 0) { require_sesskey(); if (!$userid) { - $userid = required_param('userid', PARAM_INT); + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + if ($blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + } + + // Userid is required, if not found by blindid. + if (!$userid) { + $userid = required_param('userid', PARAM_INT); + } } return $this->remove_submission($userid); @@ -8469,7 +8527,17 @@ protected function process_revert_to_draft($userid = 0) { require_sesskey(); if (!$userid) { - $userid = required_param('userid', PARAM_INT); + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + if ($blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + } + + // Userid is required, if not found by blindid. + if (!$userid) { + $userid = required_param('userid', PARAM_INT); + } } return $this->revert_to_draft($userid); @@ -8640,7 +8708,17 @@ protected function process_lock_submission($userid = 0) { require_sesskey(); if (!$userid) { - $userid = required_param('userid', PARAM_INT); + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + if ($blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + } + + // Userid is required, if not found by blindid. + if (!$userid) { + $userid = required_param('userid', PARAM_INT); + } } return $this->lock_submission($userid); @@ -8689,7 +8767,17 @@ protected function process_unlock_submission($userid = 0) { require_sesskey(); if (!$userid) { - $userid = required_param('userid', PARAM_INT); + if ($this->is_blind_marking()) { + $blindid = optional_param('blindid', 0, PARAM_INT); + if ($blindid) { + $userid = $this->get_user_id_for_uniqueid($blindid); + } + } + + // Userid is required, if not found by blindid. + if (!$userid) { + $userid = required_param('userid', PARAM_INT); + } } return $this->unlock_submission($userid);
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-8jrv-wx83-w3xjghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-67857ghsaADVISORY
- access.redhat.com/security/cve/CVE-2025-67857ghsavdb-entryx_refsource_REDHATWEB
- bugzilla.redhat.com/show_bug.cgighsaissue-trackingx_refsource_REDHATWEB
- github.com/moodle/moodle/commit/ac30e7e19357f696979b7ffd760a7131b6ad88f6ghsaWEB
- github.com/moodle/moodle/commit/c6cb8d971257c04a12a2c5d8510a89cb906f46f0ghsaWEB
- moodle.org/mod/forum/discuss.phpghsaWEB
News mentions
0No linked articles in our index yet.