WebSocket Security: Hijacking, Injection, and Cross-Site Attacks
WebSocket connections occupy an unusual position in the web security landscape. They provide persistent, bidirectional communication between a browser and server — genuinely useful for chat, live data feeds, collaborative editing, and real-time dashboards. They also inherit almost none of the browser's built-in protections that govern standard HTTP requests. The result is a protocol where the application must implement security controls that the browser would ordinarily enforce automatically, and where those controls are frequently missing.
The gap between what teams assume WebSocket connections inherit and what they actually inherit is where most WebSocket vulnerabilities originate.
The Upgrade Handshake and What It Does Not Protect
A WebSocket connection begins as an HTTP request. The client sends a standard HTTP GET with an Upgrade: websocket header, the server responds with 101 Switching Protocols, and the connection transitions from HTTP to the WebSocket protocol for all subsequent communication.
GET /ws/chat HTTP/1.1
Host: app.example.internal
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://app.example.internal
Cookie: session=abc123Two things about this request matter for security. First, the browser attaches session cookies for the target domain automatically — the same behavior as any other HTTP request. Second, the Origin header identifies where the connection was initiated from, but the browser places no restriction on cross-origin WebSocket upgrade requests. Unlike cross-origin fetch calls or XMLHttpRequest, which trigger CORS preflight checks that the server must explicitly pass, a WebSocket upgrade request from any origin will be sent by the browser without any preflight negotiation.
The Origin header is present. Enforcing it is entirely the server's responsibility.
If the server accepts the upgrade without checking whether the Origin value is expected, any webpage — on any domain — can establish a WebSocket connection to that server carrying a victim's credentials.
Cross-Site WebSocket Hijacking
Cross-site WebSocket hijacking (CSWSH) is the WebSocket analog of cross-site request forgery, but with a more severe impact profile. Where CSRF forces a browser to send a single forged HTTP request, CSWSH establishes a full bidirectional connection that persists until explicitly closed.
The attack works as follows:
- A victim is authenticated to a target application that uses WebSockets
- The victim visits a page under the attacker's control
- JavaScript on the attacker's page calls
new WebSocket('wss://target.example.internal/ws/api') - The browser sends the upgrade request, attaching the victim's session cookie
- The target server accepts the upgrade without checking
Origin - The attacker's JavaScript now holds a fully authenticated WebSocket connection
- The script sends protocol messages and reads responses, acting as the victim
The attacker cannot read the victim's cookies directly — same-origin policy prevents that. But by establishing a WebSocket connection, the attacker's page gains full access to whatever the WebSocket server exposes for authenticated sessions. In applications that conduct meaningful operations over WebSocket — trading, messaging, account management, data retrieval — this translates directly to account compromise.
The distinguishing characteristic of CSWSH versus HTTP-based attacks is the bidirectional read. CSRF attacks can send forged requests, but the attacker's page cannot read the server's response due to same-origin policy. A hijacked WebSocket connection has no such limitation: the attacker's JavaScript receives every message the server sends, including sensitive data, internal state, and authentication material.
Detecting the Vulnerability
Testing an endpoint for CSWSH involves sending a WebSocket upgrade request with a modified Origin header and observing whether the server completes the handshake.
During a web application assessment, the process begins by identifying WebSocket endpoints. These appear in browser developer tools under the Network tab, filtered by WS type. For each endpoint, the upgrade request is captured and replayed with an Origin value set to an external domain:
GET /ws/api HTTP/1.1
Host: app.example.internal
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://attacker.example
Cookie: session=<captured_session_token>A 101 Switching Protocols response to this request, with no error indicating origin rejection, confirms the endpoint accepts cross-origin connections. The next step is confirming that the connection is authenticated — that the server treats the connection as the session identified by the cookie — by sending protocol messages and observing whether the server returns data specific to that user's account.
A proof-of-concept page hosted on a separate origin confirms exploitability from the browser's perspective, verifying that the browser will indeed initiate and maintain the cross-origin connection without user-visible warnings.
Missing and Weak Authentication
WebSocket authentication vulnerabilities extend beyond origin validation. Several patterns create exploitable authentication weaknesses specific to the WebSocket lifecycle.
Authentication solely at handshake time. Many implementations check that a valid session exists when the WebSocket connection is established, then maintain the connection without re-verification. A session that is invalidated — through logout, timeout, or administrative action — after a WebSocket connection is established may leave the connection active. The server trusts the connection because it was authenticated at upgrade time, but the underlying session no longer exists. Depending on the application, this creates a window where a terminated session continues to have active WebSocket access.
Credentials in the URL. WebSocket upgrade requests must be GET requests, which limits how authentication tokens can be passed. Some implementations append tokens to the WebSocket URL: wss://api.example.internal/ws?token=abc123. This approach exposes the token in server access logs, browser history, HTTP referrer headers, and any forward proxies that log request URLs. The token is effectively written to plaintext storage as a side effect of the connection establishment.
The preferred approach passes authentication through the first WebSocket message after connection, or through a cookie set at a preceding HTTP authentication flow. The WebSocket protocol itself supports subprotocol negotiation that can carry initial authentication payloads without embedding credentials in the URL.
No per-message authorization. Long-lived WebSocket connections may serve multiple types of requests within a single session. An application that exposes administrative operations over WebSocket alongside standard user operations must validate that each message's requested operation is authorized for the user the connection represents — not just that the connection is authenticated. Connection-level authentication combined with operation-level authorization is the correct model.
WebSocket Message Injection
The WebSocket protocol transmits arbitrary strings. When applications receive WebSocket messages, process their content, and relay the results to other connected clients, the content of incoming messages must be treated as untrusted input.
In a chat application, a WebSocket message containing <script>alert(1)</script> that is relayed to other users without output encoding will execute in those users' browsers when the content is rendered in an HTML context. The attack surface is identical to stored XSS in a traditional HTTP application — the only difference is the delivery mechanism.
Real-time collaborative applications, multiplayer game state synchronization, and live data platforms all relay client-supplied content to other connected clients. The output encoding requirements depend on the rendering context:
- Content displayed in HTML: HTML entity encoding for the context (element content, attribute values, script context each require different treatment)
- Content used in SQL operations: parameterized queries, not string interpolation
- Content passed to server-side commands: the same sanitization as any externally-supplied input to those commands
The speed advantage of WebSocket — low latency delivery to many recipients — amplifies the impact of injection vulnerabilities. A stored XSS payload injected through a WebSocket message in a high-traffic chat room may execute in thousands of browsers before the content is reviewed.
Denial of Service Considerations
WebSocket connections are persistent and consume server resources for their duration. Unlike HTTP requests, which have bounded resource lifetime, a WebSocket connection occupies a file descriptor, memory allocation, and potentially application state for as long as it remains open.
Applications that allow a single user to establish many simultaneous WebSocket connections, or that allow connections to trigger expensive server-side operations without rate limiting, are exposed to resource exhaustion. The mitigation combines per-user connection limits, message rate limits, and connection timeouts for idle connections that have not sent or received messages within a defined interval.
Connection timeouts carry a usability trade-off in applications where idle connections are expected — a user who leaves a dashboard open overnight does not want to find it disconnected in the morning. The appropriate timeout interval depends on the application's usage patterns, but connections with no activity after a configurable threshold should require re-authentication rather than remaining perpetually open.
Securing WebSocket Endpoints
Validate the Origin header on every upgrade request. The server should maintain an explicit allow-list of acceptable Origin values and reject any upgrade request whose Origin is absent or not on the list. This is the primary control against CSWSH:
ALLOWED_ORIGINS = {'https://app.example.internal', 'https://admin.example.internal'}
def websocket_upgrade_handler(request):
origin = request.headers.get('Origin')
if origin not in ALLOWED_ORIGINS:
return HttpResponse(403)
# Proceed with upgradeThe check should be case-sensitive and exact. Substring matching or suffix matching (checking only that the origin ends with .example.internal) can be bypassed with specially constructed origin values.
Use cookie-based or token-in-first-message authentication. Avoid placing authentication tokens in the WebSocket URL. Session cookies established through a preceding HTTP authentication flow are attached automatically and require no URL modification. If token-based authentication is required and cookies are not suitable, accept a signed token in the first WebSocket message after connection and close the connection if the token is absent or invalid within a short timeout.
Apply output encoding to all relayed content. Any message content that is stored and later rendered in a browser context requires encoding appropriate to the rendering location. This is not specific to WebSockets — it is standard input handling — but the real-time relay pattern in WebSocket applications makes it easy to build a pipeline that moves untrusted content from one client to many others without the intermediate processing that would catch injection in a traditional request-response model.
Implement rate limiting per connection and per user. Limit the number of simultaneous connections per user, the message rate per connection, and enforce timeouts for connections that have been idle beyond a threshold. These controls prevent resource exhaustion and reduce the impact of compromised accounts that attempt to use WebSocket connections for sustained automated access.
Re-verify session validity at intervals. For long-lived connections, add a periodic server-side check that the session associated with the connection remains valid. If the session has been invalidated — through logout, expiry, or administrative action — close the WebSocket connection immediately rather than leaving it open in a detached state.
WebSocket vulnerabilities are not exotic. Cross-site hijacking, message injection, and authentication issues follow patterns well-established in HTTP security — the novelty is in where the browser's automatic protections do and do not apply. Applications that treat WebSocket endpoints with the same scrutiny as REST API endpoints will address most of the attack surface. The ones that do not tend to leave origin validation unimplemented, which turns authenticated WebSocket functionality into a capability that any website can access on behalf of any victim who visits it.
For context on how CORS misconfiguration creates similar cross-origin trust issues in HTTP APIs, see the CORS misconfiguration knowledge article. For the token-based API authentication patterns that WebSocket authentication often relies on, the JWT attacks knowledge article covers common implementation flaws in token verification.