Skip to content

CWE-362: WebSocket race conditions in SharedKickPusher lead to state corruption and duplicate subscriptions #5

@BP602

Description

@BP602

Summary

WebSocket connection and subscription logic in SharedKickPusher is not correlated to a specific socket and not serialized. Stale events and concurrent subscribe/unsubscribe paths can corrupt connection state, create duplicate or orphaned subscriptions, and lead to message amplification or loss.

Severity: Medium • CWE-362 (Race Condition)

Affected component

  • utils/services/kick/sharedKickPusher.js

Precise code references

  • addChatroom() subscribes immediately while other paths may also subscribe
    • 36–54
  • connect() state gating lacks per-socket correlation
    • 69–79, 93–104
  • open handler updates global state without verifying current socket
    • 115–145
  • close handler unconditionally resets global state; stale close can clobber new connection
    • 177–227
  • message handler for pusher:connection_established sets connected, socketId, then subscribes (no socket correlation)
    • 229–261
  • subscribeToAllChannels() runs multiple async operations without serialization
    • 333–346
  • subscribeToUserEvents() has a duplicate-subscription window before userEventsSubscribed flips
    • 348–381
  • subscribeToChatroomChannels() TOCTOU on subscribedChannels Set allowing duplicate subscribe
    • 383–401
  • unsubscribeFromChatroomChannels() may race with subscribe
    • 433–446

Impact

  • Connection state corruption (e.g., connectionState becomes disconnected due to a stale close while a new socket is live).
  • Duplicate or orphaned channel subscriptions:
    • Duplicate event handling and message amplification.
    • Orphaned subscriptions missing events or leaking processing.
  • Potential DoS via rapid reconnect/subscription churn.

Evidence

  • Event handlers (open, close, message) update shared state without checking they belong to the current this.chat instance.
  • Concurrent subscription flows:
    • addChatroom() triggers subscribeToChatroomChannels() while subscribeToAllChannels() is also iterating, both checking !this.subscribedChannels.has(channel) before adding, enabling duplicates.
  • No serialization of async operations across subscribeToAllChannels(), subscribeToUserEvents(), and subscribeToChatroomChannels().

PoCs in repo

  • exploits/poc_websocket_race_conditions.py
  • exploits/poc_websocket_race_demo.js

Reproduction (one approach)

  1. Trigger multiple connection attempts quickly:
    • Call connect() multiple times in rapid succession.
  2. While connecting, rapidly add/remove chatrooms to create concurrent subscribe/unsubscribe:
    • addChatroom() / removeChatroom() in quick intervals.
  3. Force a close on the old socket (network blip), observe stale close resetting state for the new socket.
  4. Observe logs:
    • Duplicated pusher:subscribe messages.
    • connectionState toggling inconsistently.
    • Orphaned channels not present in chatrooms map.

Recommended mitigations

  • Per-connection correlation
    • Maintain a monotonically increasing connectionId for each connect().
    • Capture connectionId (or socket ref) in all bound handlers and early-return if it does not match the current connection.
    • Track and remove handlers when replacing the socket.
  • Serialize state transitions
    • Use an async mutex/semaphore around connect() and subscription flows.
    • Queue subscription/unsubscription operations; collapse duplicates and process in order.
  • Idempotency guards
    • Use pendingSubscriptions/pendingUnsubs in addition to subscribedChannels to avoid TOCTOU duplicates.
  • Robust FSM
    • Constrain permissible transitions (disconnected→connecting→connected) and drop stale events.

CWE / Severity

  • CWE-362: Race Condition
  • Severity: Medium

Environment

  • Observed in repo head at time of filing; PoCs demonstrate behavior against public Pusher endpoint.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions