Am I affected?
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
- Serve a WebSock whose
init/1returns{:stop, :normal, {4401, "unauthorized"}, state}for a wrong token, and whosehandle_in/2runs an admin-onlydelete_all_usersaction whenuser_idisadmin. - Upgrade with
?user_id=admin&token=wrong. The server callsterminate/2and sends a Close frame with code 4401. - Ignore the Close frame and send a masked text frame
delete_all_users. handle_in/2runsdelete_all_usersasadmin.
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 ↗
Affected — GitHub / mtrudel/bandit Repository ↗
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
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