CVE-2021-39163 (Python)

Matrix is an ecosystem for open federated Instant Messaging and Voice over IP. In versions 1.41.0 and prior, unauthorised users can access the name, avatar, topic and number of members of a room if they know the ID of the room. This vulnerability is limited to homeservers where the vulnerable homeserver is in the room and untrusted users are permitted to create groups (communities). By default, only homeserver administrators can create groups. However, homeserver administrators can already access this information in the database or using the admin API. As a result, only homeservers where the configuration setting `enable_group_creation` has been set to `true` are impacted. Server administrators should upgrade to 1.41.1 or higher to patch the vulnerability. There are two potential workarounds. Server administrators can set `enable_group_creation` to `false` in their homeserver configuration (this is the default value) to prevent creation of groups by non-administrators. Administrators that are using a reverse proxy could, with partial loss of group functionality, block the endpoints `/_matrix/client/r0/groups/{group_id}/rooms` and `/_matrix/client/unstable/groups/{group_id}/rooms`.

Weakness:
CWE-863: 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.

Repository: https://github.com/matrix-org/synapse

Vulnerable code:
file_path: synapse/groups/groups_server.py
snippet:     async def get_rooms_in_group(
        self, group_id: str, requester_user_id: str
    ) -> JsonDict:
        """Get the rooms in group as seen by requester_user_id

        This returns rooms in order of decreasing number of joined users
        """

        await self.check_group_is_ours(group_id, requester_user_id, and_exists=True)

        is_user_in_group = await self.store.is_user_in_group(
            requester_user_id, group_id
        )

        room_results = await self.store.get_rooms_in_group(
            group_id, include_private=is_user_in_group
        )

        chunk = []
        for room_result in room_results:
            room_id = room_result["room_id"]

            joined_users = await self.store.get_users_in_room(room_id)
            entry = await self.room_list_handler.generate_room_entry(
                room_id, len(joined_users), with_alias=False, allow_private=True
            )

            if not entry:
                continue

            entry["is_public"] = bool(room_result["is_public"])

            chunk.append(entry)

        chunk.sort(key=lambda e: -e["num_joined_members"])

        return {"chunk": chunk, "total_room_count_estimate": len(room_results)}

Fixed code:
file_path: synapse/groups/groups_server.py
snippet:     async def get_rooms_in_group(
        self, group_id: str, requester_user_id: str
    ) -> JsonDict:
        """Get the rooms in group as seen by requester_user_id

        This returns rooms in order of decreasing number of joined users
        """

        await self.check_group_is_ours(group_id, requester_user_id, and_exists=True)

        is_user_in_group = await self.store.is_user_in_group(
            requester_user_id, group_id
        )

        # Note! room_results["is_public"] is about whether the room is considered
        # public from the group's point of view. (i.e. whether non-group members
        # should be able to see the room is in the group).
        # This is not the same as whether the room itself is public (in the sense
        # of being visible in the room directory).
        # As such, room_results["is_public"] itself is not sufficient to determine
        # whether any given user is permitted to see the room's metadata.
        room_results = await self.store.get_rooms_in_group(
            group_id, include_private=is_user_in_group
        )

        chunk = []
        for room_result in room_results:
            room_id = room_result["room_id"]

            joined_users = await self.store.get_users_in_room(room_id)

            # check the user is actually allowed to see the room before showing it to them
            allow_private = requester_user_id in joined_users

            entry = await self.room_list_handler.generate_room_entry(
                room_id,
                len(joined_users),
                with_alias=False,
                allow_private=allow_private,
            )

            if not entry:
                continue

            entry["is_public"] = bool(room_result["is_public"])

            chunk.append(entry)

        chunk.sort(key=lambda e: -e["num_joined_members"])

        return {"chunk": chunk, "total_room_count_estimate": len(chunk)}