CVE-2020-28953 (published 2020-11-19T22:15Z)

In BigBlueButton before 2.2.29, a user can vote more than once in a single poll.

Severity: MEDIUM
CVSS v2 base score: 4.0 (AV:N/AC:L/Au:S/C:N/I:P/A:N)
CVSS v3 base score: 4.3, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/bigbluebutton/bigbluebutton
commit_details: repo_url: https://github.com/bigbluebutton/bigbluebutton
author: KDSBrowne
author_date: 2020-10-28 18:33:09+00:00
msg: prevent users from being able to send more than 1 poll vote
repository: repo_url: https://github.com/bigbluebutton/bigbluebutton
repo_name: bigbluebutton/bigbluebutton
description: Complete open source web conferencing system.
repo_language: JavaScript
owner: bigbluebutton
file_changes: filename: Polls.scala
change_type: ModificationType.MODIFY
diff: @@ -313,7 +313,10 @@ object Polls {
   def respondToQuestion(pollId: String, questionID: Int, responseID: Int, responder: Responder, polls: Polls) {
     polls.polls.get(pollId) match {
       case Some(p) => {
-        p.respondToQuestion(questionID, responseID, responder)
+        if (!p._responders.exists(_ == responder)) {
+          p.addResponder(responder)
+          p.respondToQuestion(questionID, responseID, responder)
+        }
       }
       case None =>
     }
@@ -455,6 +458,11 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
   private var _stopped: Boolean = false
   private var _showResult: Boolean = false
   private var _numResponders: Int = 0
+  var _responders = new ArrayBuffer[Responder]()
+
+  def addResponder(responder: Responder) {
+    _responders += (responder)
+  }
 
   def showingResult() { _showResult = true }
   def showResult(): Boolean = { _showResult }

programming_language: Scala
filename: publishVote.js
change_type: ModificationType.MODIFY
diff: @@ -8,12 +8,21 @@ export default function publishVote(pollId, pollAnswerId) {
   const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'RespondToPollReqMsg';
-
   const { meetingId, requesterUserId } = extractCredentials(this.userId);
 
   check(pollAnswerId, Number);
   check(pollId, String);
 
+  const waitingFor = Polls.findOne({ id: pollId }, {
+    feilds: {
+      users: 1,
+    },
+  });
+
+  const userResponded = !waitingFor.users.includes(requesterUserId);
+
+  if (userResponded) return null;
+
   const selector = {
     users: requesterUserId,
     meetingId,
@@ -43,11 +52,11 @@ export default function publishVote(pollId, pollAnswerId) {
       return Logger.error(`Removing responded user from Polls collection: ${err}`);
     }
 
-    return Logger.info(`Removed responded user=${requesterUserId} from poll (meetingId: ${meetingId}, `
+    Logger.info(`Removed responded user=${requesterUserId} from poll (meetingId: ${meetingId}, `
       + `pollId: ${pollId}!)`);
+
+    return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
   };
 
   Polls.update(selector, modifier, cb);
-
-  return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
 }

programming_language: TypeScript
filename: service.js
change_type: ModificationType.MODIFY
diff: @@ -1,8 +1,13 @@
 import { makeCall } from '/imports/ui/services/api';
 import Polls from '/imports/api/polls';
+import { debounce } from 'lodash';
 
 const MAX_CHAR_LENGTH = 5;
 
+const handleVote = (pollId, answerId) => {
+  makeCall('publishVote', pollId, answerId.id);
+};
+
 const mapPolls = () => {
   const poll = Polls.findOne({});
   if (!poll) {
@@ -30,9 +35,7 @@ const mapPolls = () => {
     },
     pollExists: true,
     amIRequester,
-    handleVote(pollId, answerId) {
-      makeCall('publishVote', pollId, answerId.id);
-    },
+    handleVote: debounce(handleVote, 500, { leading: true, trailing: false }),
   };
 };
 

programming_language: TypeScript