WebSocket Security - Sicherheitsrisiken in Echtzeit-Kommunikation
WebSockets enable bidirectional real-time communication between browsers and servers. Security risks arise from a lack of origin validation (cross-site WebSocket hijacking), a lack of authentication after the handshake, unencrypted ws:// connections instead of wss:// connections, and a lack of input validation in WebSocket messages. Protection: Validate Origin headers, enforce wss://, and use token authentication during the handshake.
WebSockets are a communication protocol that enables a persistent, bidirectional connection between a browser and a server. Compared to traditional HTTP requests, WebSockets have specific security characteristics: They are not protected by the Same-Origin Policy like normal requests, can be modified by proxies, and the initial handshake differs significantly from the subsequent data exchange.
WebSocket Protocol and Security Model
A WebSocket connection is established in three steps:
Step 1 - HTTP Upgrade Request (Handshake):
GET /ws HTTP/1.1
Host: app.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://app.example.com ← IMPORTANT! Must be validated!
Cookie: sessionid=abc123 ← Session cookie is sent along!
Step 2 - Server Response (101 Switching Protocols):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Step 3 - WebSocket Frames (bidirectional): After the handshake, communication takes place via the raw WebSocket protocol—no longer via HTTP. HTTP headers are no longer sent with each message, and browsers do not send cookies in WebSocket frames. Authentication must therefore be implemented via the WebSocket protocol itself.
Key Differences from HTTP
| Feature | HTTP | WebSocket |
|---|---|---|
| Same-Origin Policy | Applies | Does not apply to WebSockets |
| CORS | Access-Control headers are checked | No WebSocket CORS |
| CSRF tokens | Apply to HTTP requests | Do not apply to WebSockets |
These differences enable the specific WebSocket attack: Cross-Site WebSocket Hijacking (CSWSH).
Cross-Site WebSocket Hijacking (CSWSH)
CSWSH is possible if the server does not validate the Origin header and authentication is performed via session cookies (which are automatically sent during the handshake).
Attack Flow:
- The attacker’s site
evil.comopens a WebSocket connection tovictim.com - The browser automatically sends the
victim.comsession cookie during the handshake - The server authenticates the connection (cookie is valid) without checking the Origin
- The attacker sends API commands as the logged-in victim
- Server responses are forwarded to the attacker’s domain
The victim simply needs to visit evil.com—for example, via a phishing link. The attacker can then use the full WebSocket API as the victim: read messages, execute actions, and exfiltrate data.
CSWSH is more powerful than standard CSRF because not only are side effects (such as changing a password) possible, but the attacker can also read all server responses—a bidirectional session takeover.
Other WebSocket Vulnerabilities
Lack of Authentication After Handshake
If authentication occurs only during connection establishment and not for each message, attackers can continue to use the still-active WebSocket connection after invalidating the session token (logging out in another tab):
ws.onmessage = (msg) => {
const data = JSON.parse(msg.data);
// Assumption: Anyone who is connected is authenticated
processCommand(data.command); // NO further auth check!
}
WebSocket Injection
WebSocket messages are just as vulnerable to injection attacks as HTTP parameters:
// SQL injection via WebSocket:
ws.send('{"query": "' + user_input + '"}');
// Payload: {"query": "' OR '1'='1"}
// → Classic SQLi, but via the WebSocket channel
XSS via WebSocket broadcast is particularly dangerous: If the server sends un sanitized user input to all connected clients and the client renders the content insecurely, a stored XSS attack occurs that affects all active users simultaneously. Countermeasure: Output encoding and textContent instead of HTML rendering.
Missing TLS (ws:// instead of wss://)
ws://app.example.com/ws is an unencrypted WebSocket connection. A man-in-the-middle can intercept all transmitted data—particularly critical for persistent, long-lived WebSocket connections. The rule is absolute: always use wss:// (WebSocket Secure over TLS).
Denial-of-Service via Message Flooding
WebSocket connections are persistent. Without rate limiting, a single client can overload the server by sending messages rapidly. In broadcasting architectures, an amplification factor also arises: an attacker’s client can flood many other clients with messages.
Message Replay
WebSocket messages have no built-in replay protection. A message recorded in Burp Suite can be resent as often as desired—for example, a “Execute Payment” message. Protection: Embed nonces or timestamps in all messages.
Detection in Penetration Testing
WebSocket Testing with Burp Suite
1. Intercept WebSocket connections: Proxy → WebSockets History displays all recorded WebSocket frames. Individual messages can be resent and modified in Burp Repeater.
2. CSWSH Test:
- Intercept the WebSocket handshake in Burp
- Change the Origin header to
evil.com - Resend the handshake
- Connection successful despite different Origin? → CSWSH vulnerability confirmed
3. Input fuzzing via WebSocket: Burp Intruder can fuzz WebSocket messages with payloads: SQL injection payloads in string fields, XSS payloads in user-viewable fields, path traversal in file references.
4. Authentication Test:
- Establish a WebSocket connection
- Log out in another browser
- Continue sending messages via the original WebSocket
- Still authenticated? → Session binding issue
5. TLS Check: ws:// instead of wss:// is an immediate critical finding. Also check whether ws:// connections are allowed as a downgrade fallback.
Mitigation Measures
1. Origin validation during handshake (critical!)
// Node.js (ws library):
const wss = new WebSocketServer({ server });
wss.on('connection', (ws, request) => {
const origin = request.headers.origin;
const allowedOrigins = ['https://app.example.com', 'https://www.example.com'];
if (!allowedOrigins.includes(origin)) {
ws.close(4000, 'Forbidden Origin');
return;
}
// Legitimate connection...
});
# Python (websockets):
async def handler(websocket, path):
origin = websocket.request_headers.get('Origin')
if origin not in ALLOWED_ORIGINS:
await websocket.close(code=4000, reason='Forbidden')
return
2. Token Authentication During Handshake
In addition to the cookie, a CSRF token should be sent during the handshake:
GET /ws?token=<csrf-token>
The server validates the token against the database or session. The token should be short-lived (valid for 5 minutes) and invalidated after the connection is established (single-use).
3. Force wss:// (Nginx WebSocket Proxy)
server {
listen 443 ssl;
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Reject ws:// connections - no downgrade fallback allowed.
4. Rate limiting for WebSocket messages
let messageCount = 0;
setInterval(() => { messageCount = 0; }, 1000); // Reset every second
ws.on('message', (msg) => {
messageCount++;
if (messageCount > 100) { // Max 100 messages/second
ws.close(4029, 'Too Many Requests');
return;
}
processMessage(msg);
});
5. Input Validation in WebSocket Messages
- JSON Schema validation for all incoming messages
- Prepared statements for all database accesses
- Output encoding for all outgoing messages
textContentinstead of HTML rendering for user-generated content- Message size limit (e.g., maximum 64 KB)
6. Idle timeout for WebSocket connections
Close connections after 30 minutes of inactivity and enforce re-authentication. This prevents session persistence after a logout and limits the window for attacks on active connections.