WebSocket Security
detect, understand, remediate
WebSocket flaws live in the gap between a single authorised upgrade and a long-lived bidirectional channel. Missing origin validation enables cross-site WebSocket hijacking. Missing per-message authorisation lets a low-privileged session reach privileged actions. Missing rate limits and frame-size caps turn a single socket into a denial-of-service primitive.
No credit card required. Free plan available forever.
What are WebSocket security vulnerabilities?
WebSocket vulnerabilities are flaws in how an application opens, authenticates, validates, and trusts a long-lived bidirectional channel created by the ws:// or wss:// protocol. The attack surface is wider than a typical HTTP endpoint because the protocol upgrade happens once, the channel stays open, and every subsequent message rides the original authorisation decision rather than being re-authorised per request.
The most impactful WebSocket bugs come from missing origin validation on the upgrade handshake (which enables cross-site WebSocket hijacking, CWE-1385), trusting client-supplied data inside long-lived sessions, lack of per-message authorisation, and weak rate limiting on a channel where a single client can sustain thousands of messages per second. The result is a class of findings that traditional HTTP-only scanners often miss because the protocol upgrade is a single request and the abuse happens after.
The vulnerability class is tracked under several CWE entries depending on the variant: CWE-1385 (missing standard error mechanism for cross-origin), CWE-346 (origin validation error), CWE-285 (improper authorisation), and CWE-770 (allocation of resources without limits). Reporting cleanly means naming the variant explicitly rather than tagging every WebSocket finding as the same generic class. The severity calibration research covers how to score variants consistently against CVSS and SSVC.
How cross-site WebSocket hijacking works
Locate the WebSocket endpoint
The attacker fingerprints the application for an upgrade endpoint, often /ws, /socket, /api/v1/ws, or a Socket.IO transport path. The endpoint URL is enumerable from the front-end JavaScript or from observing real traffic.
Confirm origin is unchecked
The attacker initiates an upgrade from a controlled origin and inspects the handshake response. If the server returns 101 Switching Protocols and the channel is authorised under the victim's cookies, origin validation is missing.
Host the malicious page
A page on the attacker's domain runs JavaScript that opens a WebSocket to the victim application. The browser attaches the victim's cookies during the handshake exactly as it would for a same-origin request, because there is no equivalent of the CORS preflight on WebSocket upgrades.
Exfiltrate or manipulate
The attacker reads incoming messages (chat content, real-time data, account updates) and writes outgoing messages on behalf of the victim (state changes, transactions, configuration updates). The session is authorised; the origin is not.
Variants seen in the wild
Cross-site WebSocket hijacking is the headline finding, but a real engagement turns up several adjacent variants. Each one needs its own remediation step. A fix that addresses only the headline case usually leaves at least one variant working.
| Variant | What goes wrong | Why it survives a partial fix |
|---|---|---|
| Cross-site WebSocket hijacking | No origin check on the upgrade. Attacker page opens a channel under the victim's session cookies. | A CSRF token on REST endpoints does nothing for WebSocket upgrades; the browser does not run a preflight on the upgrade request. |
| Missing per-message authorisation | Every message after the upgrade is treated as authorised because the upgrade itself was authorised. Privileged operations ride the open socket without re-checks. | Adding origin validation closes the hijacking case but leaves a low-privileged user able to send privileged messages from a legitimate session. |
| Session hijacking via leaked token | Authentication token sent in the WebSocket URL query string ends up in proxy logs, browser history, or referer headers, then gets replayed. | Switching to header-based auth closes the leak; an existing token already in logs remains usable until rotated. |
| Message tampering on unencrypted ws:// | The application accepts plain ws:// upgrades on internal networks or behind reverse proxies, leaving messages readable and modifiable in transit. | Forcing wss:// at the public edge can leave internal hops unencrypted if the load balancer terminates TLS and forwards plain ws:// downstream. |
| Denial of service via unbounded sockets | No per-IP or per-account cap on concurrent sockets, no per-socket rate limit on inbound messages, no payload size limit per frame. | A connection cap alone does not stop a single socket from sending millions of small messages or one giant frame that exhausts the buffer. |
| Subprotocol confusion | Server negotiates a WebSocket subprotocol it does not actually parse correctly, leading to message smuggling or framing inconsistencies between client and server. | Adding subprotocol validation at the handshake misses cases where the subprotocol is accepted but interpreted differently by each side. |
| Cross-protocol smuggling via WebSocket | An attacker uses the WebSocket channel to relay arbitrary bytes to a downstream service that does not expect WebSocket framing, abusing the trust between the WebSocket gateway and the backend. | Tightening the WebSocket gateway alone leaves the backend unprotected; the fix has to include input validation on the downstream service as well. |
Common causes
Treating the WebSocket upgrade as a same-origin request
Frameworks default to accepting any origin on the WebSocket handshake because there is no preflight. Developers assume same-origin policy applies and never add an explicit origin allowlist on the upgrade endpoint.
One authorisation decision for the whole socket
The application checks identity and role at the upgrade and treats every subsequent message as authorised. Sensitive operations that travel over the socket bypass the per-action authorisation that the equivalent REST endpoint would enforce.
Token in the URL query string
Native WebSocket APIs do not let JavaScript set custom headers on the upgrade, so authentication tokens get appended to the URL. Tokens then leak through reverse-proxy access logs, browser history, and analytics pipelines, especially if the WebSocket sits behind a CDN.
No origin validation at the gateway
The reverse proxy or API gateway terminates the WebSocket and forwards it to the backend without re-checking the Origin header, even when the backend is configured to trust the gateway implicitly.
Trusting client-supplied user identifiers in messages
Once the channel is open, the server reads a user_id or tenant_id field from each incoming message rather than from the session, allowing an authenticated user to operate as another user inside the same socket.
No idle timeout or session revocation
A WebSocket opened during a valid session stays connected after the session is revoked or rotated, so a logged-out or password-changed user retains an active authorised channel until the connection is forcibly closed.
How to detect it
Automated detection
- SecPortal's authenticated scanner fingerprints WebSocket endpoints from the in-app JavaScript, drives the upgrade handshake with a manipulated Origin header, and flags configurations where 101 Switching Protocols is returned for cross-origin requests carrying the session cookie.
- The external scanner enumerates WebSocket endpoints on common paths, checks whether ws:// is accepted alongside wss://, and flags handshakes that complete without an authentication challenge on a public endpoint.
- Findings carry the upgrade request, response, and a transcript of post-upgrade messages, so the chain stays reproducible during retest rather than getting reconstructed under time pressure.
Manual testing
- Open the WebSocket from a controlled origin using a small HTML file and the native WebSocket API. If the upgrade succeeds and messages flow under the victim cookie, the application is missing origin validation.
- Inspect every message sent and received during a real session. For each privileged action (state change, transaction, config update), confirm whether the action is also reachable directly via a crafted message rather than only through the in-app UI flow.
- Inject a different user_id or tenant_id field into outgoing messages from a low-privileged session and check whether the server accepts the value or rejects against the session-bound identity.
- Open a socket, log out the same user from another tab, then attempt to send a privileged message on the original socket. A correctly-implemented server invalidates the channel; many do not.
- Saturate the channel with high-frequency small messages and oversized frames to exercise rate limits and payload caps. Lack of either is a finding even if the application appears to handle the load gracefully under low traffic.
How to fix it
Validate the Origin header on every upgrade
Configure the application or gateway to allowlist a fixed set of origins on the WebSocket handshake. Reject any upgrade where the Origin header is missing, malformed, or not in the allowlist. This is the single most effective control against cross-site WebSocket hijacking.
Re-authorise on every message, not just on upgrade
Treat each inbound message as a separate authorisation event. Read the actor from the session bound to the socket, never from a field in the message body, and check the action against the actor's permissions before executing.
Send authentication tokens in headers, not URLs
Where the deployment supports it, use Sec-WebSocket-Protocol or a custom upgrade header to carry the authentication token instead of appending it to the URL. If the token has to ride the URL for compatibility, use a short-lived ticket exchanged for a session cookie immediately on connect, then invalidate the ticket.
Force wss:// end-to-end, including internal hops
Reject ws:// at the public edge with a 4xx response rather than upgrading silently. Configure the load balancer and any internal mesh to keep TLS in place to the backend, or to use a separate authenticated transport on internal hops.
Bound concurrent sockets and per-socket message rate
Enforce per-IP and per-account caps on concurrent open sockets. Apply a per-socket rate limit on inbound messages and a maximum frame size. Surface excesses as alerts rather than only blocking, so abuse patterns are visible in the audit trail.
Tie the socket lifetime to the session lifetime
When a session is revoked, password rotated, or token invalidated, force-close every WebSocket bound to that session. The channel must not outlive the authorisation that opened it.
Validate subprotocols at the handshake
If the application advertises a subprotocol, validate it explicitly during negotiation and reject upgrades that request unsupported subprotocols rather than accepting and ignoring. Mismatched subprotocols between client and server are a known source of message-framing bugs.
Strip tokens from logs and error messages
Configure reverse proxies, CDNs, and observability tooling to redact tokens that appear in the WebSocket URL. Audit access logs and error reports for leaked tokens periodically and rotate any that are found.
Reporting and triage in the engagement
WebSocket findings are easy to under-report because the upgrade looks innocuous in a captured request and the abuse only shows in the message stream that follows. The credible report includes the upgrade request, the upgrade response, a transcript of the abusive messages, and a proof that the same operation reaches the backend without a per-message authorisation check. SecPortal's findings management attaches the upgrade exchange and the message transcript to the same finding so the chain stays attached during retest rather than being reconstructed.
Severity calibration is the trap. Cross-site WebSocket hijacking on a chat surface is medium because the realistic impact is message disclosure under social-engineering conditions. The same hijacking on an admin console that issues state changes over the socket is critical because the attacker reaches privileged actions without re-auth. Score against the action set the socket actually exposes, not against the protocol abstractly. The finding triage guide covers how to defend severity calls when the client pushes back.
Retest scope must include every variant tested in the original engagement, not just the one the developer fixed. Remediation tracking keeps the original variant matrix attached to the retest, and the retest workflow guide covers how to script the variant suite so it can be replayed exactly during verification. Where the engagement involves authenticated scanner output that flagged the upgrade endpoint, the false positive triage guide covers how to validate the finding before it ships to the client report.
Compliance impact
Catch WebSocket findings before they ship to the client
SecPortal drives WebSocket upgrades with manipulated Origin headers, captures the post-upgrade message transcript, and keeps the variant matrix attached to the finding through retest. Start free.
No credit card required. Free plan available forever.