VYPR
Moderate severityOSV Advisory· Published Feb 3, 2026· Updated Feb 3, 2026

Moodle: moodle: data exposure of user identifiers in urls

CVE-2025-67857

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.

PackageAffected versionsPatched versions
moodle/moodlePackagist
< 4.1.224.1.22
moodle/moodlePackagist
>= 4.4.0-beta, < 4.4.124.4.12
moodle/moodlePackagist
>= 4.5.0-beta, < 4.5.84.5.8
moodle/moodlePackagist
>= 5.0.0-beta, < 5.0.45.0.4
moodle/moodlePackagist
>= 5.1.0-beta, < 5.1.15.1.1

Affected products

2
  • Moodle/MoodleOSV2 versions
    v1.0.0, v1.0.1, v1.0.2, …+ 1 more
    • (no CPE)range: v1.0.0, v1.0.1, v1.0.2, …
    • (no CPE)

Patches

2
ac30e7e19357

MDL-82808 assign: Use blindid during submission removal in blind marking

https://github.com/moodle/moodleMihail GeshoskiDec 1, 2025via ghsa
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.
    
c6cb8d971257

MDL-82808 mod_assign: replace userid with blindid in anonymous marking

https://github.com/moodle/moodleRajneel TotaramNov 18, 2025via ghsa
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

News mentions

0

No linked articles in our index yet.