CVE-2020-27602 (published 2022-09-29T03:15Z)

BigBlueButton before 2.2.7 does not have a protection mechanism for separator injection in meetingId, userId, and authToken.

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

Weakness information:
cwe_id: CWE-74
cwe_name: Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')
description: The product constructs all or part of a command, data structure, or record using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify how it is parsed or interpreted when it is sent to a downstream component.
extended_description: Software or other automated logic has certain assumptions about what constitutes data and control respectively. It is the lack of verification of these assumptions for user-controlled input that leads to injection problems. Injection problems encompass a wide variety of issues -- all mitigated in very different ways and usually attempted in order to alter the control flow of the process. For this reason, the most effective way to discuss these weaknesses is to note the distinct features that classify them as injection weaknesses. The most important issue to note is that all injection problems share one thing in common -- i.e., they allow for the injection of control plane data into the user-controlled data plane. This means that the execution of the process may be altered by sending code in through legitimate data channels, using no other mechanism. While buffer overflows, and many other flaws, involve the use of some further issue to gain execution, injection problems need only for the data to be parsed.
         

Fix information:
repo_url: https://github.com/bigbluebutton/bigbluebutton
commit_details: repo_url: https://github.com/bigbluebutton/bigbluebutton
author: Tiago Jacobs
author_date: 2020-04-29 00:33:45-03:00
msg: Refactor connection definition of userId to wait for validateAuthToken
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: validateAuthToken.js
change_type: ModificationType.MODIFY
diff: @@ -2,6 +2,9 @@ import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
 import Users from '/imports/api/users';
 import userJoin from './userJoin';
+import pendingAuthenticationsStore from '../store/pendingAuthentications';
+import createDummyUser from '../modifiers/createDummyUser';
+import setConnectionIdAndAuthToken from '../modifiers/setConnectionIdAndAuthToken';
 
 const clearOtherSessions = (sessionUserId, current = false) => {
   const serverSessions = Meteor.server.sessions;
@@ -12,12 +15,58 @@ const clearOtherSessions = (sessionUserId, current = false) => {
 };
 
 export default function handleValidateAuthToken({ body }, meetingId) {
-  const { userId, valid, waitForApproval } = body;
+  const { userId, valid, authToken, waitForApproval } = body;
 
   check(userId, String);
+  check(authToken, String);
   check(valid, Boolean);
   check(waitForApproval, Boolean);
 
+  const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken);
+
+  if(!valid) {
+    pendingAuths.forEach ( 
+      pendingAuth => {
+        try {
+          const {methodInvocationObject} = pendingAuth;
+          const connectionId = methodInvocationObject.connection.id;
+
+          methodInvocationObject.connection.close();
+          Logger.info(`Closed connection ${connectionId} due to invalid auth token.`);
+        } catch (e) {
+          Logger.error(`Error closing socket for meetingId '${meetingId}', userId '${userId}', authToken ${authToken}`);
+        }
+      }
+    );
+    
+    return;
+  }
+
+  if(valid) {
+    // Define user ID on connections
+    pendingAuths.forEach ( 
+        pendingAuth => {
+            const {methodInvocationObject} = pendingAuth;
+
+            /* Logic migrated from validateAuthToken method ( postponed to only run in case of success response ) - Begin */
+            const sessionId = `${meetingId}--${userId}`;
+            methodInvocationObject.setUserId(sessionId);
+
+            const User = Users.findOne({
+                meetingId,
+                userId: userId,
+            });
+        
+            if (!User) {
+                createDummyUser(meetingId, userId, authToken);
+            }
+        
+            setConnectionIdAndAuthToken(meetingId, userId, methodInvocationObject.connection.id, authToken);
+            /* End of logic migrated from validateAuthToken */
+        }
+    );
+  }
+
   const selector = {
     meetingId,
     userId,

programming_language: TypeScript
filename: validateAuthToken.js
change_type: ModificationType.MODIFY
diff: @@ -1,35 +1,22 @@
 import { Meteor } from 'meteor/meteor';
 import RedisPubSub from '/imports/startup/server/redis';
 import Logger from '/imports/startup/server/logger';
-import Users from '/imports/api/users';
-import createDummyUser from '../modifiers/createDummyUser';
-import setConnectionIdAndAuthToken from '../modifiers/setConnectionIdAndAuthToken';
+import pendingAuthenticationsStore from '../store/pendingAuthentications';
 
 export default function validateAuthToken(meetingId, requesterUserId, requesterToken) {
   const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
   const EVENT_NAME = 'ValidateAuthTokenReqMsg';
 
-  const sessionId = `${meetingId}--${requesterUserId}`;
-  this.setUserId(sessionId);
-
-  const User = Users.findOne({
-    meetingId,
-    userId: requesterUserId,
-  });
-
-  if (!User) {
-    createDummyUser(meetingId, requesterUserId, requesterToken);
-  }
-
-  setConnectionIdAndAuthToken(meetingId, requesterUserId, this.connection.id, requesterToken);
+  // Store reference of methodInvocationObject ( to postpone the connection userId definition )
+  pendingAuthenticationsStore.add(meetingId, requesterUserId, requesterToken, this);
 
   const payload = {
     userId: requesterUserId,
     authToken: requesterToken,
   };
 
-  Logger.info(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}'`);
+  Logger.info(`User '${requesterUserId}' is trying to validate auth token for meeting '${meetingId}' from connection '${this.connection.id}'`);
 
   return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
 }

programming_language: TypeScript
filename: pendingAuthentications.js
change_type: ModificationType.ADD
diff: @@ -0,0 +1,43 @@
+import Logger from '/imports/startup/server/logger';
+
+class PendingAuthentitcations {
+    constructor () {
+        Logger.debug("PendingAuthentitcations :: constructor");
+        this.store = [];
+    }
+
+    generateKey (meetingId, userId, authToken) {
+        // Protect against separator injection
+        meetingId = meetingId.replace(/ /g, '');
+        userId    = userId.replace(/ /g, '');
+        authToken = authToken.replace(/ /g, '');
+
+        // Space separated key
+        return '${meetingId} ${userId} ${authToken}';
+    }
+
+    add (meetingId, userId, authToken, methodInvocationObject) {
+        Logger.debug("PendingAuthentitcations :: add", {meetingId, userId, authToken});
+        this.store.push({
+            key: this.generateKey(meetingId, userId, authToken),
+            meetingId, userId, authToken, methodInvocationObject
+        });
+    }
+
+    take (meetingId, userId, authToken) {
+        Logger.debug("PendingAuthentitcations :: take", {meetingId, userId, authToken});
+        const key = this.generateKey(meetingId, userId, authToken);
+        
+        // find matches
+        const matches = this.store.filter( e => e.key === key );
+        
+        // remove matches (if any)
+        if(matches.length) { 
+            this.store = this.store.filter( e => e.key !== key ) ;
+        }
+
+        // return matches
+        return matches;
+    }
+}
+export default new PendingAuthentitcations();
\ No newline at end of file

programming_language: TypeScript