Client-Side Enforcement of Server-Side Security vulnerability in the WebSocket connection handling of mtrudel bandit allows a remote client…
6b3ad84c-e1a6-4bf7-a703-f496b71e49db·CWE-602·Published 2026-10-26
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.
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.
## Summary 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. ## Details **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`. ## Impact 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. ## 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.
| Version | Type | Source | Base | Exp | Impact | Vector |
|---|---|---|---|---|---|---|
| 4.0 | Primary | cve.org | 6.3 | — | — | 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 |
| 4.0 | Secondary | NVD | 6.3 | — | — | 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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X |
| 4.0 | Secondary | ENISA EUVD | 6.3 | — | — | 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 |