Skip to content
winfunc
Disclosure record · Jitsi Meet

Regex room-claim case mutation permits unauthorized protected-meeting access

Lowercasing a signed Lua pattern changed the room policy after JWT verification

Jitsi MeetDisclosedSource record

CVSS 3.1 base score

High
7.1/ 10

Vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N

Record

Project
Jitsi Meet
Severity
High7.1
CVE
None listed
Disclosed
Trace
3 steps

Summary

Source

init_session()

resources/prosody-plugins/mod_jitsi_session.lua:32

Sink

Util:verify_room()

resources/prosody-plugins/token/util.lib.lua:457

A participant with a legitimate regex-scoped JWT could join a protected room that the issuer's signed pattern did not authorize. A claim such as ^team-%D+$ was transformed into ^team-%d+$, turning a non-digit policy into a digit policy before the MUC join check.

Root cause. Util:verify_room() lowercased the room claim before deciding whether it was a literal room or a Lua pattern. Case conversion is safe for a canonical room identifier, but it mutates pattern operators such as %D, %A, %S, and %W into operators with different meanings.

Remediation evidence. Jitsi opened PR #17803 the day after the disclosure and merged it on 10 September 2026. The patch preserves regex claims byte-for-byte, canonicalizes literal claims separately, requires a full-string match, and adds paired uppercase/lowercase pattern regression tests.

Severity

Attack vectorAV
Network
Attack complexityAC
Low
Privileges requiredPR
Low
User interactionUI
None
ScopeS
Unchanged
ConfidentialityC
High
IntegrityI
Low
AvailabilityA
None

Metric values as published in the disclosure vector. Meters show how far each value raises exposure.

Source-to-sink trace

  1. Source · attacker-controlledresources/prosody-plugins/mod_jitsi_session.lua:32

    init_session()

  2. Step 01resources/prosody-plugins/mod_jitsi_session.lua:32

    The public BOSH/WebSocket session accepts a valid bearer JWT and stores it on the authoritative Prosody session.

    python
    if request.headers["authorization"] then
      token = request.headers["authorization"]:sub(8)
    end
    session.auth_token = token
    
  3. Step 02resources/prosody-plugins/token/util.lib.lua:335

    Signature, issuer, and audience verification succeeds, after which the signed room expression and regex marker become session policy.

    python
    local claims = jwt.verify(session.auth_token, algorithm, key, issuers, audiences)
    session.jitsi_meet_room = claims["room"]
    session.jitsi_meet_context_room = claims["context"]["room"]
    
  4. Step 03resources/prosody-plugins/token/util.lib.lua:457

    The vulnerable verifier lowercased pattern syntax, so the resulting expression authorized a different set of rooms than the issuer signed.

    python
    local auth_room = string.lower(session.jitsi_meet_room)
    local result = string.match(room_node, auth_room)
    
  5. Sinkresources/prosody-plugins/token/util.lib.lua:457

    Util:verify_room()

Impact

Reported impact

The attacker crosses the issuer-defined room boundary and joins an existing protected meeting as a participant, gaining its chat, signaling, and media participation path. The finding does not claim moderator privileges or a service-wide outage.

Attack surface

Public Jitsi BOSH or XMPP WebSocket authentication and MUC join handling in deployments that enable token verification and regex room claims.

Preconditions

The attacker needs an ordinary valid participant JWT containing an affected uppercase Lua pattern class, and a same-tenant room that matches the lowercased pattern but not the signed pattern. No signing key or moderator credential is required.

Attack path

  1. 1

    Obtain a legitimate token scoped to a regex such as ^team-%D+$.

  2. 2

    Authenticate normally over BOSH or WebSocket with that token.

  3. 3

    Join an existing room such as team-123, which the original signed pattern excludes.

  4. 4

    The verifier lowercases %D to %d and admits the participant to the protected room.

Proof of concept

Reproduction5 stages
  1. 01

    Environment setup

    Run an isolated token-authenticated Jitsi deployment on a vulnerable revision and create the protected room team-123.

  2. 02

    Target configuration

    Issue an ordinary participant JWT with room: "^team-%D+$" and context.room.regex: true. The token must otherwise have a valid signature, issuer, audience, and tenant scope.

  3. 03

    Exploit delivery

    Authenticate using the legitimate token, then send a normal MUC join presence for team-123@conference.example.test/attacker.

  4. 04

    Expected response

    The vulnerable server returns successful self-presence and conference signaling even though team-123 does not match the signed %D policy.

  5. 05

    Outcome

    PR #17803 keeps regex text unchanged and requires a complete match, so the same join is rejected while genuinely matching rooms remain accessible.

Remediation

Guidance

Upgrade to a Jitsi Meet release containing PR #17803. Preserve signed regex text exactly, lowercase only literal room identifiers, validate pattern syntax, and require a full-resource match.

Before and after−2+4
local auth_room = string.lower(session.jitsi_meet_room)local result = string.match(room_node, auth_room)local regex_room = context_room and context_room.regex == trueif not regex_room then auth_room = string.lower(auth_room) endlocal first, last = string.find(room_node, auth_room)if first ~= 1 or last ~= #room_node then return false end
View original source record

Check the upstream record for the project's current remediation status.

Your codebase

Investigate the paths that matter in your codebase.

Winfunc can trace relevant code paths, preserve supporting evidence, and prepare remediation suggestions for engineering review within an agreed scope.