Moderate severityNVD Advisory· Published Jun 1, 2015· Updated May 6, 2026
CVE-2015-3180
CVE-2015-3180
Description
lib/navigationlib.php in Moodle through 2.5.9, 2.6.x before 2.6.11, 2.7.x before 2.7.8, and 2.8.x before 2.8.6 allows remote authenticated users to obtain sensitive course-structure information by leveraging access to a student account with a suspended enrolment.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
moodle/moodlePackagist | < 2.6.11 | 2.6.11 |
moodle/moodlePackagist | >= 2.7.0, < 2.7.8 | 2.7.8 |
moodle/moodlePackagist | >= 2.8.0, < 2.8.6 | 2.8.6 |
Affected products
35cpe:2.3:a:moodle:moodle:*:*:*:*:*:*:*:*+ 34 more
- cpe:2.3:a:moodle:moodle:*:*:*:*:*:*:*:*range: <=2.5.9
- cpe:2.3:a:moodle:moodle:2.5.0:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.1:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.2:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.3:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.4:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.5:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.6:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.7:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.5.8:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.0:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.1:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.10:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.2:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.3:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.4:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.5:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.6:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.7:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.8:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.6.9:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.0:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.1:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.2:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.3:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.4:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.5:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.6:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.7.7:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.0:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.1:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.2:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.3:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.4:*:*:*:*:*:*:*
- cpe:2.3:a:moodle:moodle:2.8.5:*:*:*:*:*:*:*
Patches
4032f18c4a50dMDL-49788 navigation: Ensure we only check active enrolments
1 file changed · +10 −11
lib/navigationlib.php+10 −11 modified@@ -1172,7 +1172,7 @@ public function initialise() { // course node and not populate it. // Not enrolled, can't view, and hasn't switched roles - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { if ($coursenode->isexpandable === true) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user access to a course has been revoked (one way or another) after @@ -1188,9 +1188,7 @@ public function initialise() { $canviewcourseprofile = false; break; } - } - - if ($coursenode->isexpandable === false) { + } else if ($coursenode->isexpandable === false) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user has been granted access to a course (one way or another) after initially // logging in for this session. @@ -1235,7 +1233,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -1274,7 +1272,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -2324,7 +2322,7 @@ protected function load_for_user($user=null, $forceforcontext=false) { $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING); } - if (can_access_course($usercourse, $user->id)) { + if (can_access_course($usercourse, $user->id, '', true)) { $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php', array('id'=>$usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', '')); } @@ -2423,6 +2421,7 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype } else if ($coursetype == self::COURSE_CURRENT) { $parent = $this->rootnodes['currentcourse']; $url = new moodle_url('/course/view.php', array('id'=>$course->id)); + $canexpandcourse = $this->can_expand_course($course); } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) { // Nothing to do here the above statement set $parent to the category within mycourses. @@ -2502,7 +2501,7 @@ protected function can_expand_course($course) { $cache = $this->get_expand_course_cache(); $canexpand = $cache->get($course->id); if ($canexpand === false) { - $canexpand = isloggedin() && can_access_course($course); + $canexpand = isloggedin() && can_access_course($course, null, '', true); $canexpand = (int)$canexpand; $cache->set($course->id, $canexpand); } @@ -2858,7 +2857,7 @@ public function initialise() { break; case self::TYPE_COURSE : $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST); - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { // Thats OK all courses are expandable by default. We don't need to actually expand it we can just // add the course node and break. This leads to an empty node. $this->add_course($course); @@ -3252,7 +3251,7 @@ private function get_course_categories() { } $categories[] = $categorynode; } - if (is_enrolled(context_course::instance($this->page->course->id))) { + if (is_enrolled(context_course::instance($this->page->course->id), null, '', true)) { $courses = $this->page->navigation->get('mycourses'); } else { $courses = $this->page->navigation->get('courses'); @@ -4149,7 +4148,7 @@ protected function generate_user_settings($courseid, $userid, $gstitle='usercurr } } else { $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext); - $userisenrolled = is_enrolled($coursecontext, $user->id); + $userisenrolled = is_enrolled($coursecontext, $user->id, '', true); if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) { return false; }
b7d307e80761MDL-49788 navigation: Ensure we only check active enrolments
1 file changed · +10 −11
lib/navigationlib.php+10 −11 modified@@ -1167,7 +1167,7 @@ public function initialise() { // course node and not populate it. // Not enrolled, can't view, and hasn't switched roles - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { if ($coursenode->isexpandable === true) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user access to a course has been revoked (one way or another) after @@ -1183,9 +1183,7 @@ public function initialise() { $canviewcourseprofile = false; break; } - } - - if ($coursenode->isexpandable === false) { + } else if ($coursenode->isexpandable === false) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user has been granted access to a course (one way or another) after initially // logging in for this session. @@ -1230,7 +1228,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -1269,7 +1267,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -2319,7 +2317,7 @@ protected function load_for_user($user=null, $forceforcontext=false) { $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING); } - if (can_access_course($usercourse, $user->id)) { + if (can_access_course($usercourse, $user->id, '', true)) { $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php', array('id'=>$usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', '')); } @@ -2418,6 +2416,7 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype } else if ($coursetype == self::COURSE_CURRENT) { $parent = $this->rootnodes['currentcourse']; $url = new moodle_url('/course/view.php', array('id'=>$course->id)); + $canexpandcourse = $this->can_expand_course($course); } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) { // Nothing to do here the above statement set $parent to the category within mycourses. @@ -2497,7 +2496,7 @@ protected function can_expand_course($course) { $cache = $this->get_expand_course_cache(); $canexpand = $cache->get($course->id); if ($canexpand === false) { - $canexpand = isloggedin() && can_access_course($course); + $canexpand = isloggedin() && can_access_course($course, null, '', true); $canexpand = (int)$canexpand; $cache->set($course->id, $canexpand); } @@ -2853,7 +2852,7 @@ public function initialise() { break; case self::TYPE_COURSE : $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST); - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { // Thats OK all courses are expandable by default. We don't need to actually expand it we can just // add the course node and break. This leads to an empty node. $this->add_course($course); @@ -3247,7 +3246,7 @@ private function get_course_categories() { } $categories[] = $categorynode; } - if (is_enrolled(context_course::instance($this->page->course->id))) { + if (is_enrolled(context_course::instance($this->page->course->id), null, '', true)) { $courses = $this->page->navigation->get('mycourses'); } else { $courses = $this->page->navigation->get('courses'); @@ -4137,7 +4136,7 @@ protected function generate_user_settings($courseid, $userid, $gstitle='usercurr } } else { $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext); - $userisenrolled = is_enrolled($coursecontext, $user->id); + $userisenrolled = is_enrolled($coursecontext, $user->id, '', true); if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) { return false; }
8b4568500b30MDL-49788 navigation: Ensure we only check active enrolments
1 file changed · +10 −11
lib/navigationlib.php+10 −11 modified@@ -1173,7 +1173,7 @@ public function initialise() { // course node and not populate it. // Not enrolled, can't view, and hasn't switched roles - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { if ($coursenode->isexpandable === true) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user access to a course has been revoked (one way or another) after @@ -1189,9 +1189,7 @@ public function initialise() { $canviewcourseprofile = false; break; } - } - - if ($coursenode->isexpandable === false) { + } else if ($coursenode->isexpandable === false) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user has been granted access to a course (one way or another) after initially // logging in for this session. @@ -1236,7 +1234,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -1275,7 +1273,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -2328,7 +2326,7 @@ protected function load_for_user($user=null, $forceforcontext=false) { $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING); } - if (can_access_course($usercourse, $user->id)) { + if (can_access_course($usercourse, $user->id, '', true)) { $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php', array('id' => $usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', '')); } @@ -2430,6 +2428,7 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype } else if ($coursetype == self::COURSE_CURRENT) { $parent = $this->rootnodes['currentcourse']; $url = new moodle_url('/course/view.php', array('id'=>$course->id)); + $canexpandcourse = $this->can_expand_course($course); } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) { // Nothing to do here the above statement set $parent to the category within mycourses. @@ -2509,7 +2508,7 @@ protected function can_expand_course($course) { $cache = $this->get_expand_course_cache(); $canexpand = $cache->get($course->id); if ($canexpand === false) { - $canexpand = isloggedin() && can_access_course($course); + $canexpand = isloggedin() && can_access_course($course, null, '', true); $canexpand = (int)$canexpand; $cache->set($course->id, $canexpand); } @@ -2865,7 +2864,7 @@ public function initialise() { break; case self::TYPE_COURSE : $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST); - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { // Thats OK all courses are expandable by default. We don't need to actually expand it we can just // add the course node and break. This leads to an empty node. $this->add_course($course); @@ -3270,7 +3269,7 @@ private function get_course_categories() { } // Don't show the 'course' node if enrolled in this course. - if (!is_enrolled(context_course::instance($this->page->course->id))) { + if (!is_enrolled(context_course::instance($this->page->course->id, null, '', true))) { $courses = $this->page->navigation->get('courses'); if (!$courses) { // Courses node may not be present. @@ -4166,7 +4165,7 @@ protected function generate_user_settings($courseid, $userid, $gstitle='usercurr } } else { $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext); - $userisenrolled = is_enrolled($coursecontext, $user->id); + $userisenrolled = is_enrolled($coursecontext, $user->id, '', true); if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) { return false; }
271477f593c4MDL-49788 navigation: Ensure we only check active enrolments
1 file changed · +10 −11
lib/navigationlib.php+10 −11 modified@@ -1167,7 +1167,7 @@ public function initialise() { // course node and not populate it. // Not enrolled, can't view, and hasn't switched roles - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { if ($coursenode->isexpandable === true) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user access to a course has been revoked (one way or another) after @@ -1183,9 +1183,7 @@ public function initialise() { $canviewcourseprofile = false; break; } - } - - if ($coursenode->isexpandable === false) { + } else if ($coursenode->isexpandable === false) { // Obviously the situation has changed, update the cache and adjust the node. // This occurs if the user has been granted access to a course (one way or another) after initially // logging in for this session. @@ -1230,7 +1228,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -1269,7 +1267,7 @@ public function initialise() { // If the user is not enrolled then we only want to show the // course node and not populate it. - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { $coursenode->make_active(); $canviewcourseprofile = false; break; @@ -2311,7 +2309,7 @@ protected function load_for_user($user=null, $forceforcontext=false) { $usercoursenode->add(get_string('notes', 'notes'), $url, self::TYPE_SETTING); } - if (can_access_course($usercourse, $user->id)) { + if (can_access_course($usercourse, $user->id, '', true)) { $usercoursenode->add(get_string('entercourse'), new moodle_url('/course/view.php', array('id'=>$usercourse->id)), self::TYPE_SETTING, null, null, new pix_icon('i/course', '')); } @@ -2410,6 +2408,7 @@ public function add_course(stdClass $course, $forcegeneric = false, $coursetype } else if ($coursetype == self::COURSE_CURRENT) { $parent = $this->rootnodes['currentcourse']; $url = new moodle_url('/course/view.php', array('id'=>$course->id)); + $canexpandcourse = $this->can_expand_course($course); } else if ($coursetype == self::COURSE_MY && !$forcegeneric) { if (!empty($CFG->navshowmycoursecategories) && ($parent = $this->rootnodes['mycourses']->find($course->category, self::TYPE_MY_CATEGORY))) { // Nothing to do here the above statement set $parent to the category within mycourses. @@ -2489,7 +2488,7 @@ protected function can_expand_course($course) { $cache = $this->get_expand_course_cache(); $canexpand = $cache->get($course->id); if ($canexpand === false) { - $canexpand = isloggedin() && can_access_course($course); + $canexpand = isloggedin() && can_access_course($course, null, '', true); $canexpand = (int)$canexpand; $cache->set($course->id, $canexpand); } @@ -2845,7 +2844,7 @@ public function initialise() { break; case self::TYPE_COURSE : $course = $DB->get_record('course', array('id' => $this->instanceid), '*', MUST_EXIST); - if (!can_access_course($course)) { + if (!can_access_course($course, null, '', true)) { // Thats OK all courses are expandable by default. We don't need to actually expand it we can just // add the course node and break. This leads to an empty node. $this->add_course($course); @@ -3239,7 +3238,7 @@ private function get_course_categories() { } $categories[] = $categorynode; } - if (is_enrolled(context_course::instance($this->page->course->id))) { + if (is_enrolled(context_course::instance($this->page->course->id), null, '', true)) { $courses = $this->page->navigation->get('mycourses'); } else { $courses = $this->page->navigation->get('courses'); @@ -4129,7 +4128,7 @@ protected function generate_user_settings($courseid, $userid, $gstitle='usercurr } } else { $canviewusercourse = has_capability('moodle/user:viewdetails', $coursecontext); - $userisenrolled = is_enrolled($coursecontext, $user->id); + $userisenrolled = is_enrolled($coursecontext, $user->id, '', true); if ((!$canviewusercourse && !$canviewuser) || !$userisenrolled) { return false; }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
12- github.com/advisories/GHSA-688p-pgj4-77hhghsaADVISORY
- moodle.org/mod/forum/discuss.phpnvdVendor AdvisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2015-3180ghsaADVISORY
- openwall.com/lists/oss-security/2015/05/18/1nvdWEB
- github.com/moodle/moodle/commit/032f18c4a50d472cddd2cb52a627d19b75921f16ghsaWEB
- github.com/moodle/moodle/commit/271477f593c4acbb84c620015fad19f08282629eghsaWEB
- github.com/moodle/moodle/commit/8b4568500b305f7ddedbca355b73ce34ea4afbc0ghsaWEB
- github.com/moodle/moodle/commit/b7d307e80761e1c5b310958223640055d23b83f6ghsaWEB
- web.archive.org/web/20200228054132/http://www.securityfocus.com/bid/74729ghsaWEB
- web.archive.org/web/20201030042703/http://www.securitytracker.com/id/1032358ghsaWEB
- www.securityfocus.com/bid/74729nvd
- www.securitytracker.com/id/1032358nvd
News mentions
0No linked articles in our index yet.