Nextcloud · Report #3636758
Cross-room signaling injection enabling unauthorized force-mute
How it was found
Summary:
A low-privileged participant can read another participant's live Talk sessionId from the room participant list, then use moderator rights in an unrelated room they control to inject moderator-only internal-signaling control messages into the victim's room session.
The server checks moderator permissions only against the sender session's own room, but it queues the message to any attacker-supplied recipient sessionId. Message delivery is then keyed only by recipient session ID, not by room. I reproduced this by sending a forceMute control from room B into a victim active in room A.
When the victim is in a call, the shipped client handles forceMute by muting the local user session.
Product / Version
- Nextcloud Server:
34.0.0.1(status.phpreportedversionstring: 34.0.0 dev) - Talk app:
24.0.0-dev.2 spreedcommit:031e0708bde280b2a83943a0e7a6102d8eede207- Server commit used in the lab:
b71661a38bb40fbdfc071f273ea506a4a9fae091 - Reproduced with the default internal-signaling mode on a local Nextcloud lab
Affected Code
lib/Controller/RoomController.php:1217-1220Active participant session IDs are exposed in the participant list.lib/Controller/RoomController.php:1250-1253Single-session participants are returned withsessionIds.lib/Controller/SignalingController.php:387-418controlmessages are authorized against the sender's session, then queued to arbitrarydecodedMessage['to'].lib/Signaling/Messages.php:48-56Queue entries storesender,recipient, andmessageonly.lib/Signaling/Messages.php:102-110Messages are fetched solely byrecipient = $sessionId.lib/Controller/SignalingController.php:551-585Victim polling consumes any queued message for that session.src/utils/webrtc/simplewebrtc/simplewebrtc.js:108-113IncomingforceMutecauses the client to mute itself whenpeerIdmatches the current session.
Steps To Reproduce:
Preconditions:
- Victim and attacker both participate in room A.
- Attacker is only a normal participant in room A.
- Attacker owns or moderates unrelated room B.
- Internal signaling is enabled.
Steps:
-
On nextcloud spreed (34.0.0.1 / Talk 24.0.0-dev.2),
victimcreated room A and addedattacker(evil). Both joined room A. evil queried GET /ocs/v2.php/apps/spreed/api/v4/room/97oqymwq/participants?format=json and received victim’s live room-A session ID JRal9O65.... -
evil created unrelated room B, joined it, and got room-B sender session pekgCNiV....
-
evil sent this to room B:
{ "messages": "[{"ev":"message","sessionId":"<evil_room_b_session>","fn":"{\"type\":\"control\",\"to\":\"<victim_room_a_session>\",\"payload\":{\"action\":\"forceMute\",\"peerId\":\"<victim_room_a_session>\"}}"}]" } to POST /ocs/v2.php/apps/spreed/api/v3/signaling/pfo7kdcz?format=json
-
Server-side evidence showed a queued row with sender pekgCNiV... and recipient JRal9O65... in oc_talk_internalsignaling.
-
victim then polled room A with GET /ocs/v2.php/apps/spreed/api/v3/signaling/97oqymwq?format=json and received the injected control message with "from":"pekgCNiV...".
-
Negative control: sending the same forceMute from room A itself, using evil’s room-A session QBL3zUuH..., created no victim-targeted queue row and victim’s next poll returned only usersInRoom.
Supporting Material/References:
Here the attacker reads the session of the victim in the shared room {F5634941}
the attacker uses the victim's session as the recipient and the attackers room B as the sender {F5634940}
victim in room A receives injected message from attacker in room B and is muted {F5634942}
Impact
- A low-privileged user can inject moderator-only internal-signaling controls into a room they do not moderate.
- The reproduced payload is
forceMute. When the victim is in a call, the shipped client handles this by muting the local session. - More generally, the room boundary for internal signaling is broken because delivery is keyed only by recipient session ID.