WebSocket Security - Sicherheitsrisiken in Echtzeit-Kommunikation
WebSockets ermöglicht bidirektionale Echtzeit-Kommunikation zwischen Browser und Server. Sicherheitsrisiken entstehen durch fehlende Origin-Validierung (Cross-Site WebSocket Hijacking), fehlende Authentifizierung nach Handshake, unverschlüsselte ws:// statt wss://-Verbindungen und fehlende Input-Validierung in WebSocket-Messages. Schutz: Origin-Header validieren, wss:// erzwingen, Token-Authentifizierung beim Handshake.
WebSockets sind ein Kommunikationsprotokoll das eine persistente, bidirektionale Verbindung zwischen Browser und Server ermöglicht. Gegenüber klassischen HTTP-Requests haben WebSockets besondere Sicherheitseigenschaften: Sie sind nicht durch Same-Origin-Policy geschützt wie normale Requests, können durch Proxies modifiziert werden, und der initiale Handshake unterscheidet sich deutlich vom späteren Datenaustausch.
WebSocket-Protokoll und Sicherheitsmodell
Eine WebSocket-Verbindung wird in drei Schritten aufgebaut:
Schritt 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 ← WICHTIG! Muss validiert werden!
Cookie: sessionid=abc123 ← Session-Cookie wird mitgesendet!
Schritt 2 - Server-Antwort (101 Switching Protocols):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Schritt 3 - WebSocket-Frames (bidirektional): Nach dem Handshake läuft die Kommunikation über das Raw WebSocket-Protokoll - nicht mehr über HTTP. Pro Nachricht werden keine HTTP-Header mehr gesendet und Browser senden keine Cookies in WebSocket-Frames. Authentifizierung muss daher über das WebSocket-Protokoll selbst implementiert werden.
Wichtige Unterschiede zu HTTP
| Merkmal | HTTP | WebSocket |
|---|---|---|
| Same-Origin-Policy | Greift | Greift nicht bei WebSockets |
| CORS | Access-Control-Header werden geprüft | Kein WebSocket-CORS |
| CSRF-Tokens | Greifen für HTTP-Requests | Greifen nicht für WebSockets |
Diese Unterschiede ermöglichen den spezifischen WebSocket-Angriff: Cross-Site WebSocket Hijacking (CSWSH).
Cross-Site WebSocket Hijacking (CSWSH)
CSWSH ist möglich wenn der Server den Origin-Header nicht validiert und die Authentifizierung über Session-Cookies erfolgt (die beim Handshake automatisch mitgesendet werden).
Angriffsverlauf:
- Die Angreifer-Seite
evil.comöffnet eine WebSocket-Verbindung zuvictim.com - Der Browser sendet automatisch den
victim.com-Session-Cookie beim Handshake mit - Der Server authentifiziert die Verbindung (Cookie ist gültig) ohne den Origin zu prüfen
- Der Angreifer sendet API-Befehle als eingeloggtes Opfer
- Server-Responses werden an die Angreifer-Domain weitergeleitet
Das Opfer muss lediglich evil.com besuchen - zum Beispiel über einen Phishing-Link. Der Angreifer kann dann die vollständige WebSocket-API als Opfer nutzen: Nachrichten lesen, Aktionen ausführen und Daten exfiltrieren.
CSWSH ist mächtiger als normales CSRF, weil nicht nur Seiteneffekte (wie Passwort ändern) möglich sind, sondern der Angreifer auch alle Server-Responses lesen kann - eine bidirektionale Übernahme der Sitzung.
Weitere WebSocket-Schwachstellen
Fehlende Authentifizierung nach Handshake
Wenn Authentifizierung nur beim Verbindungsaufbau stattfindet und nicht pro Nachricht, können Angreifer nach einer Invalidierung des Session-Tokens (Logout in einem anderen Tab) die noch aktive WebSocket-Verbindung weiter nutzen:
ws.onmessage = (msg) => {
const data = JSON.parse(msg.data);
// Annahme: Wer verbunden ist, ist authentifiziert
processCommand(data.command); // KEINE weitere Auth-Prüfung!
}
WebSocket-Injection
WebSocket-Messages sind genauso anfällig für Injection-Angriffe wie HTTP-Parameter:
// SQL-Injection via WebSocket:
ws.send('{"query": "' + user_input + '"}');
// Payload: {"query": "' OR '1'='1"}
// → Klassische SQLi, nur über WebSocket-Kanal
XSS über WebSocket-Broadcast ist besonders gefährlich: Wenn der Server User-Input unbereinigt an alle verbundenen Clients sendet und der Client den Inhalt unsicher rendert, entsteht ein Stored-XSS-Angriff der alle aktiven Nutzer gleichzeitig betrifft. Gegenmaßnahme: Output-Encoding und textContent statt HTML-Rendering.
Fehlende TLS (ws:// statt wss://)
ws://app.example.com/ws ist eine unverschlüsselte WebSocket-Verbindung. Ein Man-in-the-Middle kann alle übertragenen Daten mitlesen - besonders kritisch bei persistenten, langlebigen WebSocket-Verbindungen. Die Regel ist absolut: immer wss:// (WebSocket Secure über TLS).
Denial-of-Service durch Message-Flooding
WebSocket-Verbindungen sind persistent. Ohne Rate-Limiting kann ein einzelner Client den Server durch schnell gesendete Nachrichten überlasten. Bei Broadcasting-Architekturen entsteht zudem ein Amplification-Faktor: ein Angreifer-Client kann viele andere Clients mit Nachrichten fluten.
Message-Replay
WebSocket-Messages haben keine eingebaute Replay-Protection. Eine in Burp Suite aufgezeichnete Nachricht kann beliebig oft erneut gesendet werden - zum Beispiel eine „Zahlung ausführen”-Nachricht. Schutz: Nonces oder Timestamps in alle Messages einbetten.
Erkennung im Pentest
WebSocket-Testing mit Burp Suite
1. WebSocket-Verbindungen abfangen: Proxy → WebSockets History zeigt alle aufgezeichneten WebSocket-Frames. Einzelne Messages können in Burp Repeater erneut gesendet und modifiziert werden.
2. CSWSH-Test:
- WebSocket-Handshake in Burp abfangen
- Origin-Header auf
evil.comändern - Handshake erneut senden
- Verbindung erfolgreich trotz anderer Origin? → CSWSH-Anfälligkeit bestätigt
3. Input-Fuzzing über WebSocket: Burp Intruder kann WebSocket-Messages mit Payloads fuzzen: SQL-Injection-Payloads in String-Felder, XSS-Payloads in User-angezeigte Felder, Path-Traversal in Datei-Referenzen.
4. Authentifizierungs-Test:
- WebSocket-Verbindung herstellen
- In einem anderen Browser ausloggen
- Über die ursprüngliche WebSocket weiter Nachrichten senden
- Noch authentifiziert? → Session-Binding-Problem
5. TLS-Prüfung: ws:// statt wss:// ist ein sofortiges kritisches Finding. Außerdem prüfen ob ws://-Verbindungen als Downgrade-Fallback erlaubt sind.
Schutzmaßnahmen
1. Origin-Validierung beim Handshake (kritisch!)
// 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;
}
// Legitime Verbindung...
});
# 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-Authentifizierung beim Handshake
Zusätzlich zum Cookie sollte ein CSRF-Token im Handshake mitgesendet werden:
GET /ws?token=<CSRF-TOKEN>
Der Server validiert den Token aus der Datenbank oder Session. Der Token sollte kurzlebig sein (5 Minuten gültig) und nach dem Verbindungsaufbau invalidiert werden (Einmalnutzung).
3. wss:// erzwingen (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";
}
}
ws://-Verbindungen ablehnen - kein Downgrade-Fallback erlaubt.
4. Rate-Limiting für WebSocket-Messages
let messageCount = 0;
setInterval(() => { messageCount = 0; }, 1000); // Reset jede Sekunde
ws.on('message', (msg) => {
messageCount++;
if (messageCount > 100) { // Max 100 Messages/Sekunde
ws.close(4029, 'Too Many Requests');
return;
}
processMessage(msg);
});
5. Input-Validierung in WebSocket-Messages
- JSON-Schema-Validierung für alle eingehenden Messages
- Prepared Statements für alle Datenbankzugriffe
- Output-Encoding für alle ausgehenden Messages
textContentstatt HTML-Rendering für User-generierte Inhalte- Message-Size-Limit (z.B. maximal 64 KB)
6. Idle-Timeout für WebSocket-Verbindungen
Verbindungen nach 30 Minuten Inaktivität schließen und Re-Authentifizierung erzwingen. Dies verhindert Session-Persistenz nach einem Logout und begrenzt das Fenster für Angriffe auf aktive Verbindungen.