Skip to content
Fast-turnaround security assessments available — 10+ years development & security experienceGet started
vulnerabilityCWE-1385OWASP A01:2021Typical severity: High

WebSocket Security: Hijacking, Injection, and Cross-Site Attacks

·10 min read

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.

http
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=abc123

Two 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:

  1. A victim is authenticated to a target application that uses WebSockets
  2. The victim visits a page under the attacker's control
  3. JavaScript on the attacker's page calls new WebSocket('wss://target.example.internal/ws/api')
  4. The browser sends the upgrade request, attaching the victim's session cookie
  5. The target server accepts the upgrade without checking Origin
  6. The attacker's JavaScript now holds a fully authenticated WebSocket connection
  7. 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:

http
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:

python
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 upgrade

The 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.

Need your application tested?

We find these vulnerabilities in real applications every day. Get a comprehensive security assessment with detailed remediation.

Request an Assessment

Summary

WebSocket connections upgrade from HTTP and inherit none of HTTP's same-origin protections. Without explicit origin validation, any website can initiate a WebSocket connection to any server that accepts the protocol upgrade — and that server will receive the connection carrying the victim's cookies. This is the foundation for cross-site WebSocket hijacking, one of several attack classes specific to persistent bidirectional connections.

Key Takeaways

  • 1WebSocket connections are initiated via an HTTP upgrade request that browsers send cross-origin without CORS restrictions, making endpoints vulnerable to cross-site hijacking if the server does not validate the Origin header
  • 2Unlike standard HTTP, WebSocket connections carry session cookies automatically and persist indefinitely — a hijacked connection provides real-time bidirectional access to whatever the WebSocket protocol exposes for as long as the connection stays open
  • 3WebSocket message content receives no automatic sanitization from the protocol; any user-supplied data relayed through WebSocket messages and rendered in a browser context requires the same encoding treatment as data in HTTP responses
  • 4WebSocket handshakes are not protected by the browser's CSRF token mechanism — only server-side Origin header validation provides equivalent protection against cross-site connection initiation
  • 5Authentication state should be re-verified after the WebSocket handshake completes and periodically during long-lived connections, because a connection may outlast the session validity it was established under

Frequently Asked Questions

Cross-site WebSocket hijacking (CSWSH) is an attack where a malicious website initiates a WebSocket connection to a target server on behalf of a victim. When the victim visits the attacker's page, JavaScript on that page sends a WebSocket upgrade request to the target server. The browser automatically includes the victim's cookies for the target server's domain in this upgrade request. If the server accepts connections without validating the Origin header, it establishes a WebSocket session authenticated as the victim. The attacker's page can then send and receive messages through this connection, accessing whatever functionality the WebSocket protocol exposes — including reading sensitive data returned to the victim's session.

HTTP requests that cross origins are subject to CORS preflight checks, and state-changing endpoints can require CSRF tokens that an attacker's page cannot obtain. WebSocket connections do not trigger CORS preflight. The browser sends the HTTP upgrade request cross-origin without any preflight negotiation, and cookies are attached automatically. Once the upgrade completes, the full duplex WebSocket connection is established. The server-side Origin header is the only mechanism that replicates the protection CORS and CSRF tokens provide for HTTP — and unlike CORS, there is no browser-enforced mechanism that blocks the upgrade if the server does not check Origin.

Three patterns appear frequently. First, treating the HTTP upgrade handshake as the sole authentication checkpoint — once the connection is established, the server trusts it indefinitely without re-verifying that the session token used to initiate it remains valid. Second, passing authentication tokens in the WebSocket URL query string rather than in headers or in the first protocol message, which exposes credentials in server access logs, browser history, and any proxies that log URLs. Third, implementing per-connection authentication without per-message authorization — a single WebSocket connection may be long-lived enough to perform operations across session boundaries, and a server that checks credentials once at upgrade time may not catch a token that was revoked mid-session.

WebSocket messages are arbitrary strings — typically JSON or plain text — and the protocol applies no sanitization. When a server receives a WebSocket message, processes its content, and broadcasts the result to other connected clients, the server is responsible for treating the incoming content as untrusted input exactly as it would treat HTTP request parameters. If message content is stored and later rendered in a browser, XSS prevention applies: output encoding based on the context where the data appears. If message content is used in database operations, parameterization applies. The WebSocket transport does not change the injection risk model; it changes how quickly the output reaches other users and how many recipients receive it simultaneously.

Testing involves three steps. First, establish a legitimate WebSocket connection through the application and capture the HTTP upgrade request, noting all headers the server requires. Second, replay a modified upgrade request with an Origin header set to an external domain you control and observe whether the server accepts the connection. A 101 Switching Protocols response confirms acceptance regardless of origin. Third, verify exploitability by crafting a proof-of-concept HTML page hosted on a separate origin that initiates the WebSocket connection and sends or receives authenticated messages. If the server accepts the upgrade and responds to protocol messages, the endpoint is vulnerable to full cross-site exploitation.