Am I affected?

type your bandit version to check

Description

Client-Side Enforcement of Server-Side Security vulnerability in the WebSocket connection handling of mtrudel bandit allows a remote client to keep running WebSock handler code after the server has stopped its connection, via data frames sent after ignoring the server's Close frame.

When a WebSock callback returns {:stop, ...}, Bandit.WebSocket.Connection calls terminate/2, sends Close and shuts down the write side, but keeps the connection process running in a :closing state. handle_frame/3 and handle_info/3 never check that state, so later frames still reach handle_in/2 and later messages reach handle_info/2, with the state from the stop tuple, until the client sends Close, TCP drops or the idle timeout fires. A client refused in init/1 after a failed token check could still run an admin-only action.

This issue affects bandit: from 0.5.1 before 1.14.0.

Technical analysis

1. Stop without close. In Bandit.WebSocket.Connection, do_stop/4 calls terminate/2, sends the Close frame and runs shutdown(:write). It then returns {:continue, %{connection | state: :closing}}, so Bandit.WebSocket.Handler keeps the process alive.

2. No state check. handle_frame/3 and handle_info/3 never check connection.state. Every later data frame still reaches handle_in/2, and every later message still reaches handle_info/2, with the state from the stop tuple.

3. Client-controlled lifetime. The connection ends only when the client sends Close, TCP drops, or the idle timeout fires, and each client frame resets that timeout. All stops through do_stop are affected: {:stop, :normal, ...}, {:stop, {:shutdown, :disconnected}, ...} and {:stop, {:shutdown, :restart}, ...}, which is what Phoenix's :socket_drain returns. A banned user who was kicked kept posting for longer than the idle timeout.

Proof of concept

  1. Serve a WebSock whose init/1 returns {:stop, :normal, {4401, "unauthorized"}, state} for a wrong token, and whose handle_in/2 runs an admin-only delete_all_users action when user_id is admin.
  2. Upgrade with ?user_id=admin&token=wrong. The server calls terminate/2 and sends a Close frame with code 4401.
  3. Ignore the Close frame and send a masked text frame delete_all_users.
  4. handle_in/2 runs delete_all_users as admin.

Weaknesses & attack patterns

Weakness

CWE-602 · Client-Side Enforcement of Server-Side Security in catalog → MITRE ↗

Attack patterns

CAPEC-202 · Create Malicious Client MITRE ↗

A remote client that ignores the Close frame can keep invoking a WebSock application's handlers after the application refused or ended its session, bypassing an authorization check in init/1 or a kick or ban. This affects WebSock applications that rely on {:stop, ...} to end a session.

Affected — Hex / bandit Hex.pm ↗ Repository ↗

≥ 0.5.1 < 1.14.0 affected
every other version: unaffected
cpe cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
version type semver

Affected — GitHub / mtrudel/bandit Repository ↗

≥ bb407f2 < 220b9df affected
every other version: unaffected
cpe cpe:2.3:a:mtrudel:bandit:*:*:*:*:*:*:*:*
version type git

Workarounds

Mark the WebSock state as stopped whenever a callback returns {:stop, ...}, and have handle_in/2 and handle_info/2 return {:ok, state} without acting while that mark is set.

References

Credits

Peter Ullrich Reporter
Mat Trudel Remediation developer
Jonatan Männchen / EEF Analyst

CVSS breakdown

CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
« All CVEs