Lion Cipher Lab
Exploit and fix Cross-Site WebSocket Hijacking (CSWSH) in your own lab
- AppSec / DevSecOps
- Intermediate
- ~60 min
- Free · no account required
Why it matters
Cross-Site WebSocket Hijacking (CSWSH) is CSRF's overlooked, more dangerous cousin. A WebSocket does not begin life as a WebSocket: it begins as a plain HTTP GET carrying an Upgrade header, and because that request is bound for the target site, the browser attaches the target's cookies to it automatically. The trap is that the Same-Origin Policy, which stops a random page from READING your webmail, does NOT stop that page from OPENING a WebSocket to your server -- there is no CORS gate on the handshake. So if a server authenticates each socket purely from the ambient session cookie and never…
What you'll learn
- explaining that a WebSocket connection begins as an ordinary HTTP GET with an Upgrade: websocket header (the RFC 6455 handshake), and that the browser attaches the target site's cookies to that handshake automatically just like any other request
- explaining the core CSWSH asymmetry: the Same-Origin Policy does NOT block a page on any origin from OPENING a WebSocket to your server, and there is no CORS preflight on the WS handshake -- so a cross-origin site can establish the connection, and if the server authenticates from the cookie alone it is authenticated as the victim
- building a tiny vulnerable Node.js app with the ws library and express that sets a session cookie on login and exposes a WS endpoint that trusts that cookie, streams private data, and accepts commands with NO Origin check
- writing a malicious HTML page served from a DIFFERENT origin that calls new WebSocket() against your app, receives the victim's private data, and exfiltrates it to the attacker's own server -- proving the session cookie rode along on the cross-origin handshake
- fixing it the durable way: a server-side exact-match Origin allowlist validated on the handshake (the primary control), rejecting any other origin
- adding a per-connection CSRF/auth token bound to the session (passed in the URL or first message) so the socket is not authorized by the ambient cookie alone -- the attacker page cannot read that token cross-origin
- explaining why a naive substring origin check like origin.includes("mysite") is broken (evil-mysite.com, mysite.attacker.com) and why an exact full-origin allowlist has nothing to trick
- judging the layered controls: why the Origin check is primary, why cookie-only auth is the root cause, why SameSite=Strict/Lax mitigates but is not a complete fix, and why production must use WSS with an exact origin match plus a session-bound token
Steps in this lab
- What CSWSH is -- the handshake, the cookie, and the missing gate
- Set up: confirm Node, make a folder, install ws and express
- Build the VULNERABLE app (the socket trusts the cookie, checks no Origin)
- Run it and confirm the legit dashboard works
- Exploit: hijack the socket from a different origin and exfiltrate
- Why the naive origin check is worthless -- and what the root cause really is
- Fix it: exact-match Origin allowlist on the handshake + a session-bound token
- Re-test: confirm the hijack is dead and the real feed still works
- The judgment: SameSite, WSS, and the pro pattern to keep