CVE-2015-10033 (JavaScript, published 2023-01-09T21:15Z)
Description: value: 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.
Severity: nan (CVSS v3 base score: 6.5)
Weakness: CWE-863 \u2014 Incorrect Authorization
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.
Repository: https://github.com/jvvlee/MerlinsBoard
Files changed:
app/assets/javascripts/collections/grades.js
app/assets/javascripts/models/grade.js
app/assets/javascripts/routers/router.js
app/assets/javascripts/views/courses/course-form.js
app/assets/javascripts/views/courses/course-list.js
app/assets/javascripts/views/courses/course_search.js
app/assets/javascripts/views/courses/courses_enroll.js
app/assets/javascripts/views/grades/grade-search-student.js
app/assets/javascripts/views/grades/grades-student.js
app/assets/templates/courses/coursesearch.jst.ejs
app/assets/templates/courses/list.jst.ejs
app/assets/templates/grades/grades-student-list.jst.ejs
app/controllers/api/grades_controller.rb
app/views/api/grades/index.json.jbuilder
config/routes.rb
db/seeds.rb
Commit message: 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


Vulnerable code:
  getOrFetch: function (id) {
    _.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
    this.student = new MerlinsBoard.Models.User({fname: resp.student_fname,lname: resp.student_lname});
})
    "course/:course_id/grades/user/:user_id" : "gradesShow"
    allcourses.fetch();
    var enrollView = new MerlinsBoard.Views.CoursesEnroll({collection: allcourses,model: this.currentUser});
				Backbone.history.navigate("",{trigger: true}) //instead do a "course created/saved"
 		this.listenTo(this.collection, "add remove sync", this.render);

		this.searchCollection = new MerlinsBoard.Collections.CoursesSearch();
	// 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);
	// }

		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.
		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch({collection: this.collection}); //render will put these in manually
	
	
    
		
    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"
MerlinsBoard.Views.SearchStudent = Backbone.View.extend({
  className: "grades-studentsearch",
    this.listenTo(this.collection, "add change:grade remove", this.render)
    //for jbuidler - nest each of a student's grade under them along with basic information about the assignment
  template: JST["grades/grades-student"],
    var renderedContent = this.template({grades: this.collection});
    return this.$el
    var gradeString = $(event.currentTarget).text();
    var $input = $("<input type='number'>").addClass('grade-input').val(num);


    $(".grade-number").html(input)
    var newGrade = $('input.grade-input').val();
    //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})
  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
  gradeSaveErrorCallback: function (model, response) {

	<li><a href="#" data-id="<%=course.id%>"><%= course.escape("name") %></a></li>
<% })%>
<h1>Grades for <%=grades.escape('fname') + " " + grades.escape('lname')%></h1>
  <h4> grade.escape('assignment_title') </h4>
  <p> grade.escape('description')</p>

//I think all I'll do is just pretend like nothing happened if the entry is invalid
  before_action(except: [:index]) {admins_only(params["course_id"])} #wil always need to pass this in on every fetch
  def destroy
    @grade = Grade.find(params[:id])
    @grade.destroy
    render json: {}
  end
    if @grade.save
    params.require(:grade).permit(:grade, :assignment_id, :user_id)
  
  json.assignment_id grade_obj.assignment_id
  json.assignment_title grade_obj.assignment.title
  json.assignment_description grade_obj.assignment.description
json.course_id @student.course.id
# weird: Cannot mix json.array! with other top-level attrs

Fixed code:
  getOrFetch: function (id, course_id) {
    _.extend(options,{ data: $.param({ course_id: this.course_id}) });
  student: function () {
    if (!this._student) {
      this._student = new MerlinsBoard.Models.User();
    }

    return this._student
  },

    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.course_id.delete

})
    "course/:course_id/grades/user/:user_id" : "gradeShow"
    this.currentUser.fetch()
    var enrollView = new MerlinsBoard.Views.CoursesEnroll({model: this.currentUser});

				Backbone.history.navigate("course/search",{trigger: true}) 
 		this.listenTo(this.collection, "reset add sync", this.render);
	searchCollection: function () {
		if (!this._searchCollection) {
			this._searchCollection = new MerlinsBoard.Collections.CoursesSearch();
		}

		return this._searchCollection
	},

		this.searchCollection().fetch({data: $.param({query: queryCourse})});
		var searchList = new MerlinsBoard.Views.CoursesList({collection: this.searchCollection()});
		//want to call remove on search results
		$('section.course-results').html(searchList.render().$el);
		this.coursesearchView = new MerlinsBoard.Views.CoursesSearch();



    return this
MerlinsBoard.Views.SearchStudentGradesResults = Backbone.View.extend({
  className: "grades-student-search",
    this.listenTo(this.collection, "add change:grade remove sync", this.render)
    //for jbuidler - nest each of a student's grade under them along with basic information about the assignmen
  template: JST["grades/grades-student-list"],
    var renderedContent = this.template({grades: this.collection, student: this.collection.student()});
    return this
    var gradeString = $(event.currentTarget).val();
    var $input = $("<input type='number' min='0' step='1' max='100'>").addClass('grade-input').val(num);
    $(".grade-number[data-id=".concat(this.modelNumber,"]")).html($input)
    var newGrade = parseInt($('input.grade-input').val());
    debugger

    editedGrade.save({course_id: courseID},{success: this.gradeSaveCallback(editedGrade),
    error: this.gradeSaveErrorCallback
  gradeSaveCallback: function (editedGrade) {
    this.collection.fetch(); //unideal - needs to be banished with composite view paradigm.
    // this.collection.add(editedGrade,{merge: true});
  gradeSaveErrorCallback: function (model, resp) {

	<li><a href="<%= '#course/' + course.id + '/enroll'%>"><%= course.escape("name") %></a></li>
<% })%>
<h1>Grades for <%= student.escape('fname') + " " + student.escape('lname')%></h1>
  <h4> Assignment name:<%= grade.escape('title') %></h4>
  <p> description: <%= grade.escape('description') %></p>
  before_action(only: [:update, :show]) {admins_only(params["course_id"])}
    @course_id = params["course_id"].to_i

  #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 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....
    if @grade.update(grade_params)
    params.permit(:grade, :assignment_id, :user_id) #need to change grade column - it confuses params_wrapper

  json.id grade_obj.id
  json.id grade_obj.assignment_id
  json.title grade_obj.assignment.title
  json.description grade_obj.assignment.description
json.course_id @course_id
# weird: Cannot mix json.array! with other top-level attrs
    resources :grades, only: [:update, :show]
User.create(fname:"John", lname: "Doe", email: "johndoe@gmail.com",
password: "testing")