AppSec · Sep 3, 2026 · 7 min read

WebSocket hijacking: confirming CSWSH without capturing a real session

An origin that is never checked during the WebSocket upgrade is reachable - confirming cross-site WebSocket hijacking requires a two-session differential probe that demonstrates authenticated data retrieval across the origin boundary.

WebSocket connections carry session cookies just like HTTP requests, but the same-origin policy that governs fetch and XMLHttpRequest does not govern the WebSocket upgrade. Any page can initiate a WebSocket upgrade to any URL - the browser attaches cookies automatically, and a server that authenticates on cookies alone without checking the Origin header grants a malicious page on a different domain a fully authenticated channel to the victim's session. The vulnerability class is Cross-Site WebSocket Hijacking (CSWSH), and it shares its root cause with CSRF: browser-automatic credential forwarding with no second factor confirming intent.

01How the upgrade opens the gap

The WebSocket handshake starts as a standard HTTP GET request with an Upgrade: websocket header and a browser-set Origin field naming the page that initiated the connection. Unlike CORS, where the browser enforces the policy on cross-origin reads by checking the response headers, the WebSocket API has no built-in cross-origin block on the upgrade itself. If the server returns 101 Switching Protocols, the connection is live and the application can begin exchanging messages. Session cookies present in the browser for the target domain travel with the upgrade request before any WebSocket-level authentication can run. The gap is not a bug in any specific library - it is the protocol design, and closing it requires an explicit server-side control.

evil.example (attacker page) | | new WebSocket('wss://api.example.com/ws') | | Browser sends: | GET /ws HTTP/1.1 | Host: api.example.com | Origin: https://evil.example (browser-set, uncontrollable by JS) | Cookie: session=abc123 (auto-attached) | Upgrade: websocket | v api.example.com | Cookie abc123 --> valid session | Origin check --> absent | Response: 101 Switching Protocols | v Attacker holds an authenticated WebSocket channel ws.onmessage = e => exfiltrate(e.data)
CSWSH upgrade flow - session cookie is forwarded automatically; the absent Origin check hands the attacker an authenticated channel

02Confirming CSWSH without targeting a real session

The confirming probe is a two-session differential. In the first session, authenticate legitimately to the target application and retain the session cookie. In the second session - sharing no credentials but running in the same browser context - initiate a WebSocket upgrade to the target URL from a controlled non-allowlisted origin. If the server returns 101, send a benign protocol message that the server would only respond to for an authenticated caller - an identity query, a resource ping tagged with a nonce, anything that returns user-specific content. A response carrying the first session's identity data is in-band proof that the second session's cross-origin connection was authenticated by the first session's cookie. The confirming artifact is the upgrade response code together with the user-specific payload. No real user is targeted, and the probe session is closed immediately after the confirmation exchange. The finding is rated reachable when the server returns 101 from a controlled origin; escalated to confirmed when the returned payload demonstrates authenticated access.

Confirmed - HighCSWSH - /ws - unauthenticated origin accepted
GET /ws HTTP/1.1
Host: api.example.com
Origin: https://probe.apposture-scan.internal
Cookie: session=abc123

HTTP/1.1 101 Switching Protocols

ws.send(JSON.stringify({type:'whoami',nonce:'62615533'}))
ws.onmessage --> {"user":"alice@corp.com","role":"admin","nonce":"62615533"}
Oracle: two-session differential. Controlled origin https://probe.apposture-scan.internal not in any allowlist; server returned 101 and echoed authenticated user identity on the nonce-tagged ping. Rated high - confirmed cross-origin session reading. Critical upgrade path when the channel accepts write-capable operations.

03Message injection and the write-capability ceiling

A confirmed CSWSH establishes that an attacker-controlled page can read messages from the victim's WebSocket channel. Severity reaches critical when the channel also accepts write-capable operations: state changes, fund transfers, privilege assignments, configuration mutations. To probe for write capability without causing real harm, send a nonce-tagged operation that would be a no-op if processed - an empty update to a field the test owns, a ping with a known-harmless type code - and observe whether the server acknowledges it with a handler response that only runs for an authenticated caller. No real state is modified during the probe; the confirming artifact is the server's acknowledgment of the nonce.

cswsh-probe.jsjavascript
// Non-destructive CSWSH confirmation - runs from a controlled origin
// No real session is targeted; probe session is closed after the nonce exchange
const NONCE = 'ap-62615533';

const ws = new WebSocket('wss://api.example.com/ws');

ws.onopen = () => {
    // Benign identity ping - any authenticated handler should echo the nonce
    ws.send(JSON.stringify({ type: 'ping', nonce: NONCE }));
};

ws.onmessage = (evt) => {
    const msg = JSON.parse(evt.data);
    if (msg.nonce === NONCE) {
        // Server echoed nonce from a non-allowlisted origin - CSWSH confirmed
        reportFinding({
            status: 'confirmed',
            user: msg.user,
            upgrade_code: '101',
            nonce: NONCE,
        });
    }
    ws.close();
};

ws.onerror = () => reportFinding({ status: 'blocked' });

Not every WebSocket protocol exposes a low-impact identity query. When the protocol carries no benign message that returns user-specific content, the upgrade response code alone is the reachable evidence - a 101 from a non-allowlisted origin confirms that Origin validation is absent. Rate the finding reachable at that step; reserve confirmed for a nonce echo or user-specific payload from the authenticated channel.

04Severity tiers, SameSite caveats, and fixes

Severity follows the highest-capability operation a hijacked channel can perform. A read-only WebSocket that leaks user identity or private data is high - cross-origin session reading is confirmed impact and warrants a 30-day SLA at most. A channel that also accepts write-capable operations is critical - arbitrary authenticated writes from an attacker-controlled page carry the same impact class as a CSRF that bypasses both token and SameSite checks, and the SLA drops to 7 days. A channel authenticated solely by a non-cookie mechanism - a per-connection token passed in the first message or a signed query-string parameter, not auto-forwarded by the browser - is not CSWSH; the browser does not auto-attach that credential and the attack path does not apply. SameSite=Strict reduces CSWSH exposure on browsers that apply SameSite to WebSocket upgrade requests, but browser implementation is not uniform across engines and versions, so it cannot stand alone as the sole control.

FixCoverageNotes
Server-side Origin allowlistCloses the gap at the upgrade stepValidate the Origin header before returning 101; reject non-listed origins with 403. This is the only control that is reliable across all browsers and WebSocket libraries.
CSRF token in upgrade URLBlocks cross-site initiationInclude a per-session unpredictable token as a query parameter; client-side JS can read it from a CSRF cookie or DOM variable that a cross-origin page cannot reach.
WebSocket-level auth in first messageAvoids cookie-forwarding entirelyAuthenticate via a signed token in the first message frame, not the HTTP upgrade; the upgrade itself is unauthenticated and checked by the first-message handler.
SameSite=Strict on session cookiePartial - browser-dependentSome browsers do not enforce SameSite on WebSocket upgrades; treat as defense-in-depth, not the primary control.
101
upgrade response that confirms Origin check is absent
2
sessions required to complete the CSWSH differential probe
7 days
SLA when the channel accepts write-capable operations

The browser's automatic cookie forwarding is a design feature that CSWSH treats as a liability. The only reliable fix is a server-side Origin allowlist evaluated before the 101 response is sent - not SameSite tuning, not a WAF rule on the Upgrade header, not per-message auth alone. A server that rejects non-allowlisted origins with 403 at the upgrade step closes the gap regardless of browser version or SameSite behavior. The confirming artifact is a 101 from a controlled non-allowlisted origin; the severity is set by what the authenticated channel is capable of returning or accepting from that unintended caller.

Evidence over heuristicsReachable beats foundMonitor → Block: rolling out a gate without slowing teamsIaC-grounded threat modelingAir-gapped AppSec with no phone-homeOne platform vs three tools
See it on your own app →