CVE-2015-10033 (published 2023-01-09T21:15Z)

A vulnerability, which was classified as problematic, was found in jvvlee MerlinsBoard. This affects an unknown part of the component Grade Handler. The manipulation leads to improper authorization. The identifier of the patch is 134f5481e2914b7f096cd92a22b1e6bcb8e6dfe5. It is recommended to apply a patch to fix this issue. The identifier VDB-217713 was assigned to this vulnerability.

CVSS v3 base score: 6.5, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:H/A:H)

Weakness information:
cwe_id: CWE-863
cwe_name: Incorrect Authorization
description: The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. This allows attackers to bypass intended access restrictions.
extended_description: 
            Assuming a user with a given identity, authorization is the process of determining whether that user can access a given resource, based on the user's privileges and any permissions or other access-control specifications that apply to the resource.
            When access control checks are incorrectly applied, users are able to access data or perform actions that they should not be allowed to perform. This can lead to a wide range of problems, including information exposures, denial of service, and arbitrary code execution.
         
         

Fix information:
repo_url: https://github.com/jvvlee/MerlinsBoard
commit_details: repo_url: https://github.com/jvvlee/MerlinsBoard
author: App Academy Student
author_date: 2015-03-19 12:34:41-04:00
msg: Progresses on grades change view and events. Discovered security vulnerability that allows a course admin to change whatever grade they want if they know the ID, even in other classes
repository: repo_url: https://github.com/jvvlee/MerlinsBoard
repo_name: jvvlee/MerlinsBoard
description: Final Project
repo_language: Ruby
owner: jvvlee
file_changes: filename: grades.js
change_type: ModificationType.MODIFY
diff: @@ -7,7 +7,7 @@ MerlinsBoard.Collections.Grades = Backbone.Collection.extend({
 
   model: MerlinsBoard.Models.Grade,
 
-  getOrFetch: function (id) {
+  getOrFetch: function (id, course_id) {
     var grade = this.get(id);
     var grades = this;
 
@@ -31,16 +31,26 @@ MerlinsBoard.Collections.Grades = Backbone.Collection.extend({
 
     //some logic here to check if "data" was already passed in, and fusing that to the data parameter...
 
-    _.extend(options,{ data: $.param({ course_id: this.course_id}) }); //options is changed
-    //with this, I might always have to bind fetch - be mindful of this in case I need to fetch more data
+    _.extend(options,{ data: $.param({ course_id: this.course_id}) });
     return Backbone.Collection.prototype.fetch.call(this, options);
   },
 
+  student: function () {
+    if (!this._student) {
+      this._student = new MerlinsBoard.Models.User();
+    }
+
+    return this._student
+  },
+
   parse: function (resp) {
-    this.student = new MerlinsBoard.Models.User({fname: resp.student_fname,lname: resp.student_lname});
+    this.student().set({fname: resp.student_fname,lname: resp.student_lname});
+    // this.course_id = resp.course_id should only set once and then never again
 
     resp.student_fname.delete //is there a better way to clean this up?
     resp.student_fname.delete
+    resp.course_id.delete
+
     return resp.grades
   }
 

programming_language: JavaScript
filename: grade.js
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
 MerlinsBoard.Models.Grade = Backbone.Model.extend({
   urlRoot: 'api/grades',
   validate: function () {}
-})
\ No newline at end of file
+})

programming_language: JavaScript
filename: router.js
change_type: ModificationType.MODIFY
diff: @@ -32,7 +32,7 @@ MerlinsBoard.Routers.Router = Backbone.Router.extend({
     "course/:course_id/assignments/:id/edit" : "editAssignment",
     //grades
     "course/:id/grades/student-search" : "gradeSearch",
-    "course/:course_id/grades/user/:user_id" : "gradesShow"
+    "course/:course_id/grades/user/:user_id" : "gradeShow"
     //misc
 //     "user/:id": "showuser"
     //":wildcard": "does not exist" --self explanatory
@@ -43,9 +43,9 @@ MerlinsBoard.Routers.Router = Backbone.Router.extend({
 	enrollcourses: function () {
     var allcourses = new MerlinsBoard.Collections.Courses([],{owner: this.currentUser});
 
-    allcourses.fetch();
+    this.currentUser.fetch()
 
-    var enrollView = new MerlinsBoard.Views.CoursesEnroll({collection: allcourses,model: this.currentUser});
+    var enrollView = new MerlinsBoard.Views.CoursesEnroll({model: this.currentUser});
     this.swapView(enrollView);
   },
 
@@ -154,6 +154,7 @@ MerlinsBoard.Routers.Router = Backbone.Router.extend({
   gradeShow: function (course_id, user_id) {
     // var course = MerlinsBoard.Courses.getOrFetch(id);
     var grades = new MerlinsBoard.Collections.Grades({course_id: course_id, user_id: user_id});
+
     grades.fetch();
 
     var gradesList = new MerlinsBoard.Views.GradesStudent({collection: grades});

programming_language: JavaScript
filename: course-form.js
change_type: ModificationType.MODIFY
diff: @@ -23,7 +23,7 @@ MerlinsBoard.Views.CourseForm = Backbone.View.extend({
 		this.model.save(attrs, {
 			success: function () {
 				MerlinsBoard.Courses.add(this.model,{merge: true})
-				Backbone.history.navigate("",{trigger: true}) //instead do a "course created/saved"
+				Backbone.history.navigate("course/search",{trigger: true}) 
 			}.bind(this),
 			error: function (model,resp) {
 				var errorArray = resp.responseJSON;

programming_language: JavaScript
filename: course-list.js
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,8 @@
 MerlinsBoard.Views.CoursesList = Backbone.View.extend({
 	initialize: function () {
- 		this.listenTo(this.collection, "add remove sync", this.render);
+ 		this.listenTo(this.collection, "reset add sync", this.render);
 	},
 
-
 	template: JST["courses/list"],
 
 	tagName: "ul",

programming_language: JavaScript
filename: course_search.js
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,5 @@
 MerlinsBoard.Views.CoursesSearch = Backbone.View.extend({
 	initialize: function () {
-		this.searchCollection = new MerlinsBoard.Collections.CoursesSearch();
 	},
 
 	template: JST["courses/coursesearch"],
@@ -11,6 +10,14 @@ MerlinsBoard.Views.CoursesSearch = Backbone.View.extend({
 		return this
 	},
 
+	searchCollection: function () {
+		if (!this._searchCollection) {
+			this._searchCollection = new MerlinsBoard.Collections.CoursesSearch();
+		}
+
+		return this._searchCollection
+	},
+
   tagName: "section",
 
   className: "course-search",
@@ -19,30 +26,14 @@ MerlinsBoard.Views.CoursesSearch = Backbone.View.extend({
 		"submit form.course-find":"search"
 	},
 
-	// search: function (event) {
-	// 	event.preventDefault();
-  //  var query = $("input.course-find-input").val();
-  //
-  //   var filtered = this.collection.filter(function (course) {
-  //     var pattern = new RegExp(query, "gi");
-  //     var result = course.get("name").match(pattern);
-  //     return result
-  //   })
-  //
-  //   var filteredCollection = new MerlinsBoard.Collections.Courses([],{owner: MerlinsBoard.CurrentUser});
-  //   filteredCollection.set(filtered);
-  //
-  //   var searchList = new MerlinsBoard.Views.CoursesList({collection: filteredCollection});
-	// 	this.$('section.course-results').html(searchList.render().$el);
-	// }
-
 	search: function (event) {
 		event.preventDefault();
 	  var queryCourse = $("input.course-find-input").val();
-		this.searchCollection.fetch({data: $.param(query: queryCourse)});
+		this.searchCollection().fetch({data: $.param({query: queryCourse})});
 
-		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection});
-		$('section.course-results').html(searchList.render.$el); //needs to be global from DOM.
+		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection()});
+		//want to call remove on search results
+		$('section.course-results').html(searchList.render().$el);
 	}
 
 })

programming_language: JavaScript
filename: courses_enroll.js
change_type: ModificationType.MODIFY
diff: @@ -1,32 +1,20 @@
 MerlinsBoard.Views.CoursesEnroll = Backbone.View.extend({
 	initialize: function () {
-		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch({collection: this.collection}); //render will put these in manually
+		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch();
 		this.usercoursesView = new MerlinsBoard.Views.CoursesList({collection: this.model.courses()});
 		this.usertaughtcoursesView = new MerlinsBoard.Views.CoursesList({collection: this.model.taughtcourses()});
 	},
-	
+
   template: JST['courses/enroll'],
-	
+
 	render: function () {
 		this.$el.html(this.template());
-    
+
     this.$("section.courses-attended").html(this.usercoursesView.render().$el);
     this.$("section.courses-taught").html(this.usertaughtcoursesView.render().$el);
     this.$("section.course-search").html(this.coursesearchView.render().$el);
-		
-    return this
-	},
-  
-  //below two, again - hardcode URLs instead
-																												
-	show: function (event) {
-		event.preventDefault();
-		var id = $(event.currentTarget).data("id");
-		Backbone.history.navigate("course/" + id + "/enroll", {trigger:true})
-	},
 
-	events: {
-		"click a": "show"
+    return this
 	}
 
 });

programming_language: JavaScript
filename: grade-search-student.js
change_type: ModificationType.MODIFY
diff: @@ -1,9 +1,9 @@
-MerlinsBoard.Views.SearchStudent = Backbone.View.extend({
+MerlinsBoard.Views.SearchStudentGradesResults = Backbone.View.extend({
   initialize: function () {
     this.listenTo(this.collection, "add remove reset", this.render)
   },
 
-  className: "grades-studentsearch",
+  className: "grades-student-search",
 
   template: JST["grades/grades-student-search"],
 

programming_language: JavaScript
filename: grades-student.js
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,12 @@
 //Lists a students grade - an admin has access to this view to edit grades
 MerlinsBoard.Views.GradesStudent = Backbone.View.extend({
   initialize: function () {
-    this.listenTo(this.collection, "add change:grade remove", this.render)
+    this.listenTo(this.collection, "add change:grade remove sync", this.render)
     _.bindAll(this, "gradeSaveCallback", "gradeSaveErrorCallback")
-    //for jbuidler - nest each of a student's grade under them along with basic information about the assignment
+    //for jbuidler - nest each of a student's grade under them along with basic information about the assignmen
   },
 
-  template: JST["grades/grades-student"],
+  template: JST["grades/grades-student-list"],
 
   events: {
     "click .grade-number":"editGrade",
@@ -18,41 +18,39 @@ MerlinsBoard.Views.GradesStudent = Backbone.View.extend({
   tagName: "section",
 
   render: function () {
-    var renderedContent = this.template({grades: this.collection});
+    var renderedContent = this.template({grades: this.collection, student: this.collection.student()});
     this.$el.html(renderedContent);
-    return this.$el
+    return this
   },
 
   editGrade: function (event) {
-    var gradeString = $(event.currentTarget).text();
+    var gradeString = $(event.currentTarget).val();
     var num = parseInt(gradeString);
-    var $input = $("<input type='number'>").addClass('grade-input').val(num);
-
+    var $input = $("<input type='number' min='0' step='1' max='100'>").addClass('grade-input').val(num);
     this.modelNumber = $(event.currentTarget).data('id');
-
-    $(".grade-number").html(input)
+    $(".grade-number[data-id=".concat(this.modelNumber,"]")).html($input)
   },
 
   saveGrade: function (event) {
     var editedGrade = this.collection.getOrFetch(this.modelNumber);
-    var newGrade = $('input.grade-input').val();
+    var newGrade = parseInt($('input.grade-input').val());
     var courseID = this.collection.course_id;
 
+    debugger
+
     editedGrade.set({grade: newGrade});
-    //two options, send in the params with the model and strong params takes care of it
-    //or send it in as an option for the save option
-    editedGrade.save({},{success: this.gradeSaveCallback,
-    error: this.gradeSaveErrorCallback,
-    data: $.param({course_id: courseID})
+    editedGrade.save({course_id: courseID},{success: this.gradeSaveCallback(editedGrade),
+    error: this.gradeSaveErrorCallback
     });
   },
 
-  gradeSaveCallback: function () {
-    this.collection.add(editedGrade,{merge: true})//this should be a closure - also editedGrade I think should persist as a variable...
-    // $(".grade-number").html(editedGrade.get('grade')); this wont work because I'm inspecific, but I may not need it anyway to rerender
+  gradeSaveCallback: function (editedGrade) {
+    this.collection.fetch(); //unideal - needs to be banished with composite view paradigm.
+    // this.collection.add(editedGrade,{merge: true});
   },
 
-  gradeSaveErrorCallback: function (model, response) {
+  gradeSaveErrorCallback: function (model, resp) {
+
     var errorArray = resp.responseJSON
     var $errorList = $("<ul>").addClass('errors');
     _.each(errorArray, function (error) {

programming_language: JavaScript
filename: coursesearch.jst.ejs
change_type: ModificationType.MODIFY
diff: @@ -10,5 +10,4 @@ all this will be is organized links-->
 </form>
 
 <section class="course-results">
-
 </section>

programming_language: Markdown
filename: list.jst.ejs
change_type: ModificationType.MODIFY
diff: @@ -1,3 +1,3 @@
 <% courses.each( function (course) { %>
-	<li><a href="#" data-id="<%=course.id%>"><%= course.escape("name") %></a></li>
-<% })%>
\ No newline at end of file
+	<li><a href="<%= '#course/' + course.id + '/enroll'%>"><%= course.escape("name") %></a></li>
+<% })%>

programming_language: CoffeeScript
filename: grades-student-list.jst.ejs
change_type: ModificationType.MODIFY
diff: @@ -1,12 +1,10 @@
-<h1>Grades for <%=grades.escape('fname') + " " + grades.escape('lname')%></h1>
+<h1>Grades for <%= student.escape('fname') + " " + student.escape('lname')%></h1>
 
 <section class="grade-errors"></section>
 
 <% grades.each(function (grade) { %>
-  <h4> grade.escape('assignment_title') </h4>
-  <p> grade.escape('description')</p>
+  <h4> Assignment name:<%= grade.escape('title') %></h4>
+  <p> description: <%= grade.escape('description') %></p>
   Grade: <strong class="grade-number" data-id="<%= grade.id %>"><%= grade.get('grade')%></strong>
   <br>
 <% }) %>
-
-//I think all I'll do is just pretend like nothing happened if the entry is invalid

programming_language: CoffeeScript
filename: grades_controller.rb
change_type: ModificationType.MODIFY
diff: @@ -1,20 +1,23 @@
 class Api::GradesController < Api::ApiController
-  before_action(except: [:index]) {admins_only(params["course_id"])} #wil always need to pass this in on every fetch
+  before_action(only: [:update, :show]) {admins_only(params["course_id"])}
   before_action :is_user_or_instructor?, only: [:index]
 
   def index
     #I may want an internal control here instead of using the before_action...
     @grades = Grade.includes(:assignment,:course,:user).where("user_id = ?", params["user_id"])
     @student = @grades.first.user
+    @course_id = params["course_id"].to_i
+
     @grades = @grades.select {|grade| grade.course.id == params["course_id"].to_i}
   end
 
-  def destroy
-    @grade = Grade.find(params[:id])
-    @grade.destroy
-    render json: {}
-  end
+  #Neither of these may be needed because they should only be created/destroyed depending on the assignment
 
+  # def destroy
+  #   @grade = Grade.find(params[:id])
+  #   @grade.destroy
+  #   render json: {}
+  # end
   # def create
   #   @grade = Grade.new(grade_params)
   #
@@ -25,10 +28,15 @@ class Api::GradesController < Api::ApiController
   #   end
   # end
 
+  def show
+    @grade = Grade.find(params[:id])
+    render json: @grade
+  end
+  #will need to create unique validator to ensure congruency between course auth and resource id course....
   def update
     @grade = Grade.find(params[:id])
 
-    if @grade.save
+    if @grade.update(grade_params)
       render json: @grades
     else
       render json: @grade.errors.full_messages, status: 422
@@ -43,7 +51,7 @@ class Api::GradesController < Api::ApiController
   end
 
   def grade_params
-    params.require(:grade).permit(:grade, :assignment_id, :user_id)
+    params.permit(:grade, :assignment_id, :user_id) #need to change grade column - it confuses params_wrapper
   end
-  
+
 end

programming_language: Ruby
filename: index.json.jbuilder
change_type: ModificationType.MODIFY
diff: @@ -1,16 +1,17 @@
 json.grades @grades do |grade_obj|
+  json.id grade_obj.id
   json.grade grade_obj.grade
-  json.assignment_id grade_obj.assignment_id
+  json.id grade_obj.assignment_id
   json.user_id grade_obj.user_id
-  json.assignment_title grade_obj.assignment.title
-  json.assignment_description grade_obj.assignment.description
+  json.title grade_obj.assignment.title
+  json.description grade_obj.assignment.description
 end
 
 json.student_fname @student.fname
 json.student_lname @student.lname
-json.course_id @student.course.id
+json.course_id @course_id
 
 #remember then that for a single model, only top-level attrs will be assigned
 # for a collection, each entry in the array should be top-level attrs (or wrapped in only a single object wrapper)
 # but the array itself must be top-level
-# weird: Cannot mix json.array! with other top-level attrs
\ No newline at end of file
+# weird: Cannot mix json.array! with other top-level attrs

programming_language: Ruby
filename: routes.rb
change_type: ModificationType.MODIFY
diff: @@ -15,6 +15,7 @@ Rails.application.routes.draw do
     resources :announcements #might have to "member do" for easy-access custom routes from a particular course
     resources :assignments
     resources :resources
+    resources :grades, only: [:update, :show]
     resources :courses do
       get "course_search", on: :collection
     end

programming_language: Ruby
filename: seeds.rb
change_type: ModificationType.MODIFY
diff: @@ -9,6 +9,8 @@
 #should create master user that has most functionality available to it.
 
 User.create(fname:"Jonathan", lname: "Lee", email: "l33.jonathan@gmail.com", password: "testing")
+User.create(fname:"John", lname: "Doe", email: "johndoe@gmail.com",
+password: "testing")
 
 (1..10).each do |user_no|
   User.create(

programming_language: Ruby