New: Cymulate Cowork for Agentic Cyber Defense Engineering
Learn More
PHP2Shell Vulnerabilities: What to Patch and How to Validate Defenses
Learn More
Cymulate named a Customers' Choice in 2026 Gartner® Peer Insights™
Learn More
Introducing Cymulate Vero AI for Agentic Cyber Defense Engineering
Learn More

CVE-2026-85267: CloudShell WSSnatch

By: Cymulate Research Lab

October 22, 2026

WSSnatch attack: a WebSocket tunnel carries an OAuth token from Google Cloud Shell port 900 to an attacker’s browser

How one-click and zero-click WebSocket attack vectors turned Google Cloud Shell into a tunnel for OAuth token theft.

Elad Beber, Senior Security Researcher
Ben Zamir, Security Researcher
Ilan Kalendarov, Former Security Research Team Lead

Executive Summary 

Cymulate Research identified a Cross-Site WebSocket Hijacking vulnerability in Google Cloud Shell that allowed a malicious website to steal an active user's OAuth access token through the browser. Tracked as CVE-2026-85267, the vulnerability received a CVSS 4.0 score of 8.7 (High) from Google Cloud. 

The attack combined three failures: a cookie sent with cross-site connections, missing WebSocket Origin validation, and a TCP tunnel that exposed an internal token service. Together, they allowed an attacker-controlled page to use the victim's authenticated Cloud Shell session to retrieve a token. 

Two delivery scenarios were demonstrated: 

  • One-click, remote: an attacker who knew the victim's Cloud Shell editor hostname could trigger token theft when the victim opened a malicious page. 
  • Zero-click, same network: an attacker on the same Layer 2 network could discover the hostname through DNS traffic and deliver the exploit through intercepted HTTP traffic, without an attacker-link click. 

Both scenarios required an active Cloud Shell session. The stolen token could access Google Cloud resources within the victim's existing permissions and carried Drive and identity scopes, extending the potential impact beyond Cloud Shell. It did not grant permissions the victim did not already have. 

Google patched the vulnerability on 22 July 2026 and published the CVE on 20 October 2026. No customer action is required. The finding highlights why authenticated WebSocket services must validate both the requesting origin and the internal destinations they expose. 

1. The finding 

Google Cloud Shell gives users an authenticated cloud environment directly in the browser. During my research, I found that a malicious website could abuse Cloud Shell’s own WebSocket connection to reach an internal service and steal Cloud Shell’s authorized user token. The token included the broad cloud-platform OAuth scope and could act with the victim’s existing Google Cloud permissions.

The attack could be delivered in two ways:

  • One-click, from anywhere: if the attacker knew the victim’s Cloud Shell hostname, opening a malicious page was enough to trigger the attack.
  • Zero-click, on the same network: a local-network attacker could learn the hostname from DNS traffic and deliver the exploit through ordinary HTTP traffic, without the victim clicking an attacker-supplied link.

Both vectors required an active Cloud Shell session in the victim’s browser.

Once the exploit page loaded, three bugs made the token theft possible:

  1. Cross-site authentication cookie: Cloud Shell set its authorization cookie with SameSite=None, so the browser also sent it with a WebSocket connection opened by a malicious website.
  2. Missing WebSocket Origin validation: the WebSocket endpoint accepted the attacker’s cross-site connection instead of restricting upgrades to trusted Google origins.
  3. Internal port exposed through the tunnel: the raw TCP tunnel allowed access to the metadata proxy on port 900, which returned the victim’s OAuth token.
  4. Together, these bugs let an untrusted website open an authenticated tunnel through the victim’s browser and reach a token that should only have been available inside the Cloud Shell environment. The one-click and zero-click versions differed only in how the exploit page reached the victim.

2. WSSnatch at a glance 

The chain’s verified properties and delivery requirements are summarized below. 


Field Details 
Affected product Google Cloud Shell 
CVE CVE-2026-85267, published 20 October 2026 · NVD 
Severity CVSS 4.0: 8.7 High, assigned by Google Cloud 
Weakness CWE-1385: Missing Origin Validation in WebSockets 
Remediation Patched 22 July 2026. No customer action required. 
Vulnerability type Cross-Site WebSocket Hijacking through an unrestricted WebSocket-to-TCP tunnel 
Demonstrated impact Theft of the active user’s authorized OAuth access token 
Observed token scopes cloud-platform, drive, and identity scopes 

Disclosure status: Google Cloud VRP accepted and rewarded the report. The official CVE record, published on 20 October 2026, includes credit for the research and states that the vulnerability was patched on 22 July 2026. Google's July 28, 2026 release notes describe the mitigation. The patch date, release-note date, and CVE publication date are separate milestones. 

The chain had two demonstrated delivery models: 


Dimension One-click, remote Zero-click, same network 
Attacker position Anywhere on the internet Same Layer 2 network as the victim 
Victim action Loads an attacker-controlled page None 
Victim session prerequisite Active Cloud Shell session in the browser Active Cloud Shell session in the browser 
Prior target knowledge Cloud Shell editor hostname None; hostname learned from observed DNS traffic 
Exploitation primitive Cross-site WebSocket Network interception followed by the same cross-site WebSocket 
Result OAuth token theft OAuth token theft 

The two variants used different delivery paths, but the server-side vulnerability was identical. 

3. How the research started 

I started this research the same way I approach any new challenge: enumerate first and make as few assumptions as possible. From inside the Cloud Shell instance, I mapped the processes, listening ports, network routes, mounts, namespaces, environment variables, and metadata endpoints to understand what was running around my shell.

The terminal was running inside a user container on a per-user Container-Optimized OS VM. That VM also hosted several supporting containers, including an orchestrator, a gateway, and a metadata proxy listening on 127.0.0.1:900. The browser reached the environment through a Google-managed editor hostname, with WebSockets used to bridge browser connections to ports inside the container.

3.1 Two identities behind metadata.google.internal 

The metadata service was where things became interesting. Querying the same metadata hostname on two different ports returned two different identities: 

Port 80: authorized user-token path

curl -sS -H "Metadata-Flavor: Google" \ 

  "http://metadata.google.internal:80/computeMetadata/v1/instance/service-accounts/" 

default/ 

<my-user-email>/ 

Port 8080: VM infrastructure identity

curl -sS -H "Metadata-Flavor: Google" \ 

  "http://metadata.google.internal:8080/computeMetadata/v1/instance/service-accounts/" 


default/ 

vms-cs-<region-and-pool>@<tenant-project>.iam.gserviceaccount.com/ 

Port 80 was not reaching the real GCE metadata service directly. A per-pod DNAT rule redirected 169.254.169.254:80 to the local metadata proxy on port 900, which injected my authorized user OAuth token. That token carried the broad cloud-platform scope, along with Google Drive and identity scopes.

Port 8080 followed a different path. It passed through to the real GCE metadata server and returned the service-account token belonging to the Cloud Shell VM. In my tests, that infrastructure identity was tightly scoped to devstorage.read_only, logging.write, and monitoring.write.

The two metadata paths looked like this:

Diagram comparing Google Cloud Shell metadata service paths on ports 80 and 8080, showing user OAuth tokens versus VM service account credentials.

The same metadata hostname resolved to a signed-in user credential on port 80 and a separate VM infrastructure credential on port 8080.

The :8080 behavior was a separate finding, but it helped me understand the architecture: Cloud Shell deliberately placed the user’s OAuth credential behind an in-container proxy, while the VM retained a separate, low-privilege infrastructure identity. For this research, the important target was the user-token proxy on port 900.

3.2 Switching from the container to the browser

Up to this point, the enumeration had been entirely backend-focused, from inside the container. I decided to switch perspectives and study the client side: how the browser provisioned the environment, authorized the session, discovered my instance, and finally connected to the terminal and editor. I captured the complete startup flow from shell.cloud.google.com to the per-session *.cloudshell.dev data plane.

3.3 Mapping the victim-specific Cloud Shell endpoint

The next step was understanding how the browser located my environment. Several different names appeared during the session, but they represented different layers of the architecture.

The underlying GCE VM had an ephemeral instance name such as cloudshell-<random-id>. That name and the VM’s IP address changed when the environment was reprovisioned, and neither was used by the browser-facing attack.

Cloud Shell also assigned the environment a logical identity based on my Google account’s numeric GAIA ID:"

cs-<GAIA-ID>-default 

default was the Cloud Shell environment name. Google then combined this identity with the regional Cloud Shell pool to build the public session hostname:

cs-<GAIA-ID>-default.<regional-pool>.cloudshell.dev

For example, a session running in the cs-europe-west4-bhnf pool followed this structure:

cs-<GAIA-ID>-default.cs-europe-west4-bhnf.cloudshell.dev

The terminal connected to that base hostname, but the editor used a port-prefixed host:

970-cs-<GAIA-ID>-default.<regional-pool>.cloudshell.dev

This distinction became important for the one-click attack. During the normal startup flow, Cloud Shell called /_cloudshell/login on the 970- editor host and set the CloudShellAuthorization cookie there. The cookie had no Domain attribute, so the browser scoped it to that exact hostname.

An attacker could not simply target any Cloud Shell URL. The remote exploit needed the victim’s full editor hostname, including both the GAIA ID and the regional pool, and had to open the WebSocket against the same 970- host:

wss://970-cs-<GAIA-ID>-default.<regional-pool>.cloudshell.dev/_cloudshell/tcp/900

That targeting requirement is what separates the remote one-click vector from the same-network zero-click vector. Remotely, the attacker needed to learn or derive the hostname in advance. On the same network, the hostname could be observed directly when the victim’s browser resolved the Cloud Shell domain.

4. Three trust boundaries that failed

While analyzing the browser’s startup traffic, I saw Cloud Shell mint an environment-bound JWT and send the browser to the 970- editor host:

GET /_cloudshell/login?token=<environment-JWT>
Host: 970-cs-<GAIA-ID>-default.<regional-pool>.cloudshell.dev

The response stored that JWT in a cookie:

Set-Cookie: CloudShellAuthorization="Bearer <environment-JWT>";
  Path=/;
  Max-Age=86400;
  HttpOnly;
  Secure;
  SameSite=None

This cookie carried the browser’s data-plane authorization state for that exact Cloud Shell environment. On later requests and WebSocket upgrades to the same 970- host, the browser attached it automatically, and the backend validated that the JWT belonged to the requested environment.

With the hostname and authentication flow mapped, I looked at what else the editor host exposed. Cloud Shell provided a WebSocket endpoint in this form:

wss://<editor-host>/_cloudshell/tcp/<port>

Once upgraded, the WebSocket acted as a raw TCP bridge to the selected port inside the user container. Cloud Shell’s web-preview feature explicitly rejected sensitive ports such as 900, but the WebSocket tunnel did not apply the same restriction. That meant a browser connection could potentially reach the metadata proxy and the user OAuth token behind it.

From there, the research became a simple chain of questions: would the browser attach the Cloud Shell authentication cookie cross-site, would the server accept a WebSocket opened by an unrelated website, and could that connection reach port 900? The answer to all three was yes.

HttpOnly prevented page JavaScript from reading the cookie value directly, but that did not stop the browser from using it. As MDN documents, HttpOnly cookies are still sent with JavaScript-initiated requests, and SameSite=None permits a cookie to be sent in both same-site and cross-site contexts.

This distinction is central to CSWSH: the malicious page does not need to read or steal the session cookie first. It asks the browser to open a connection, and the browser authenticates that connection on the page’s behalf.

SameSite=None may be necessary for some embedded product flows, so this attribute alone should not be treated as the complete vulnerability. It became exploitable because the server did not independently verify who initiated the WebSocket.

Google Cloud Shell authorization cookie response showing Set-Cookie security attributes, including SameSite=None, HttpOnly, Secure, and a 24-hour expiration window.

Figure 1. The original authorized capture showed that the Cloud Shell authorization cookie was eligible for cross-site use and remained valid for 24 hours. Sensitive values and identifiers are redacted.

4.2 Bug 2: The WebSocket upgrade trusted every Origin

Browsers add an Origin header to WebSocket handshakes. Page JavaScript cannot freely replace that header, so the server can use it to distinguish a legitimate application from an attacker-controlled website.

In testing, the Cloud Shell TCP endpoint returned 101 Switching Protocols for:

Origin: https://attacker.example
Origin: null
No Origin header
Origin: https://shell.cloud.google.com

There was no effective origin allowlist.

This removed the key server-side protection against CSWSH. The endpoint did not distinguish a WebSocket created by Cloud Shell from one created by an unrelated page. OWASP recommends validating every WebSocket handshake against an explicit allowlist and rejecting null, missing, wildcard, or substring-matched origins.

At this point, an attacker-controlled page could create an authenticated WebSocket connection. The remaining question was what that connection could reach.

Google Cloud Shell WebSocket Origin validation test showing HTTP 101 responses for malicious, null, missing, and legitimate origins, revealing insufficient Origin validation.

Figure 2. The TCP WebSocket upgraded connections regardless of whether the initiating origin was foreign, null, absent, or legitimate.

4.3 Bug 3: The tunnel reached the token service on port 900

Cloud Shell’s web preview path rejected access to internal port 900:

GET https://900-<redacted-editor-host>/

HTTP/1.1 400 Bad Request
invalid port

The WebSocket TCP tunnel did not apply the same restriction:

wss://<redacted-editor-host>/_cloudshell/tcp/900

After the WebSocket upgrade, I sent an ordinary HTTP request as raw bytes:

GET /computeMetadata/v1/instance/service-accounts/default/token HTTP/1.1
Host: metadata.google.internal
Metadata-Flavor: Google
Connection: close

Port 900 exposed the Cloud Shell environment’s metadata proxy.

{
  "access_token": "ya29.<redacted>",
  "expires_in": 3600,
  "token_type": "Bearer"
}

Google’s Compute Engine documentation documents the same metadata path and the required Metadata-Flavor: Google header. A process legitimately running inside a VM can use this endpoint to obtain an access token. The vulnerability was that an untrusted website could reach that inside-only service through an authenticated tunnel.

The web preview and raw TCP features had different port policies. One recognized port 900 as unsafe; the other exposed it.

Google Cloud Shell port 900 security comparison showing web preview blocking metadata proxy access while a raw TCP WebSocket tunnel bypasses restrictions and exposes an OAuth access token.

Figure 3. Two routes to the same port enforced different trust policies: web preview blocked port 900, while the raw TCP WebSocket exposed it.

5. The one-click attack, end to end

The remote proof of concept was intentionally small. It served a page that executed the following logic when loaded:

WSSnatch one-click attack flow diagram showing how Google Cloud Shell WebSocket vulnerabilities enable OAuth token theft through cross-site cookies, missing Origin validation, and exposed metadata services.

Figure 4. The WSSnatch one-click attack flow: SameSite=None, missing WebSocket Origin validation, and an unrestricted TCP tunnel combine to expose the victim’s Google OAuth token.

const ws = new WebSocket(
  "wss://<victim-editor-host>/_cloudshell/tcp/900"
);

ws.onopen = () => {
  ws.send(
    "GET /computeMetadata/v1/instance/service-accounts/default/token HTTP/1.1\r\n" +
    "Host: metadata.google.internal\r\n" +
    "Metadata-Flavor: Google\r\n" +
    "Connection: close\r\n\r\n"
  );
};

The browser added the Cloud Shell cookie to the handshake. The server accepted the attacker’s origin and upgraded the connection. The JavaScript then spoke HTTP through the WebSocket-to-TCP bridge, extracted the redacted ya29.* token from the response, and sent the result back to the proof-of-concept collector.

No browser extension, local malware, attacker Google account, or direct read access to the victim’s cookies was required.

The remote variant did have two important preconditions:

  • The victim needed an active Cloud Shell authentication context in the tested browser.
  • The attacker needed the victim’s editor hostname and needed the victim to load the malicious page.

6. Turning one-click into same-network zero-click

The editor hostname was the main targeting requirement in the remote scenario. I next tested whether a local-network attacker could remove it.

The same-network proof of concept combined standard local interception techniques with the same three server-side bugs:

  1. The attacker placed themselves in the victim’s network path using ARP poisoning on a shared Layer 2 network.
  2. When the victim opened Cloud Shell, DNS traffic disclosed the victim-specific Cloud Shell hostname and regional pool.
  3. The proof of concept disrupted known encrypted-DNS paths to encourage system-DNS fallback in the tested setup.
  4. It spoofed a suitable DNS response and injected an HTTP 302 redirect into intercepted plain-HTTP traffic, causing the browser to load the exploit page.
  5. The exploit page opened the authenticated WebSocket to port 900 and recovered the OAuth token.
WSSnatch zero-click attack flow diagram showing how DNS spoofing, WebSocket vulnerabilities, and cross-site cookies enable Google Cloud Shell OAuth token theft without user interaction.

Figure 5. The WSSnatch same-network flow: the attacker first enters the network path, learns the victim-specific Cloud Shell hostname, delivers the exploit through an ordinary HTTP request, and chains the three server-side bugs to recover the OAuth token.

In the tested environment, delivery could be triggered when the user made a normal plain-HTTP request. The victim did not need to click a link supplied by the attacker or enter any information into an attacker-controlled page after activating Cloud Shell.

“Zero-click” here refers to the delivery interaction, not an absence of preconditions. The attacker still needed Layer 2 network access, the victim needed an active Cloud Shell session, and the network/browser behavior had to permit the demonstrated interception path.

This is realistic on shared networks only where client isolation and related controls do not block ARP-based interception. It should not be generalized to every corporate, hotel, airport, conference, or coffee-shop network.

7. One stolen token, access far beyond Cloud Shell

The recovered token was checked against Google’s token information endpoint. In the authorized test account, it carried:

https://www.googleapis.com/auth/cloud-platform
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
openid

This was not a narrow Cloud Shell session token. The recovered credential carried Google’s global cloud-platform scope, allowing it to call Google Cloud APIs as the victim across services. The scope was Cloud-wide; the specific actions available were the actions authorized by the victim’s IAM permissions. The same token also carried the unrestricted drive scope, which Google defines as permission to view and manage all of the victim’s Drive files.

Impact: broad compromise of the victim’s Google Cloud and Google Drive access from one stolen token. An attacker could act across every Google Cloud service permitted by the victim’s IAM roles, view or modify all Drive files, and recover the account’s verified email identity and profile.

The demonstrated scope set did not include Gmail permissions or account-recovery controls. The proven impact was Cloud and Drive compromise plus Google account identity exposure, not Gmail mailbox access or password takeover.

Recovered Google Cloud Shell OAuth token scopes showing access to Google Cloud Platform, Google Drive, email, profile, and OpenID identity information.

Figure 6. The recovered token included the broad cloud-platform scope alongside Drive and identity scopes; account identifiers and the token value are excluded.

8. Why the chain was more than the sum of its parts

Each issue crossed a different trust boundary:

  • Cookie policy: the browser was allowed to authenticate a cross-site connection.
  • Handshake policy: the WebSocket server accepted an untrusted initiator.
  • Destination policy: the tunnel exposed a credential-bearing internal service.

No single layer had enough context to make the complete attack obvious. The cookie configuration may have supported an embedding requirement. The WebSocket endpoint may have assumed the cookie proved legitimacy. The tunnel may have assumed only the trusted Cloud Shell frontend could invoke it.

That is the deeper failure mode: distributed security assumptions without a single enforced policy.

The chain also demonstrates why port-forwarding features require more than network filtering. Once a browser-facing service can carry arbitrary bytes to internal destinations, it becomes a security boundary comparable to SSRF protection, a reverse proxy, and a session-authenticated API at the same time.

9. Fixing all three trust boundaries

Google's release notes describe stricter WebSocket Origin validation and an allowed port list for TCP and SSH tunnels. The official CVE record dates the patch to 22 July 2026 and identifies earlier Cloud Shell deployments as affected. Google states that no customer action is required.

Fixing any one of the three bugs would break the demonstrated chain, but all three should be addressed:

1.  Validate WebSocket origins: Allow only the exact trusted Cloud Shell origins and reject unknown, null, or missing origins.

2.  Harden browser authentication: Avoid using a cross-site cookie as the only authorization for a sensitive WebSocket. Use a stricter SameSite policy where possible or require a short-lived, connection-specific token.

3.  Restrict tunnel destinations: Apply the same port policy to every proxy and tunnel path. Internal services such as the metadata proxy on port 900 must never be reachable through the browser-facing TCP tunnel.

10. Disclosure timeline

DateEvent
24 June 2026Report submitted to Google VRP with the complete PoC package and video demonstrations. An automated receipt was received.
10 July 2026Google accepted the report and filed an internal bug with the responsible engineering team.
22 July 2026Google patched the vulnerability, according to the official CVE record.
28 July 2026Google's release notes document the mitigation for CVE-2026-85267.
6 August 2026Google Cloud VRP awarded a bounty and described the submission as “a report of exceptional quality.”
20 October 2026Google Cloud published CVE-2026-85267, gave credit for the research, and assigned CVSS 4.0: 8.7 High and CWE-1385.

I sincerely appreciate the Google Cloud VRP team’s communication throughout this process. They responded quickly, communicated the status and assessment transparently, and moved the report into remediation without delay. Their openness, professionalism, and recognition of the research made this a remarkably positive disclosure experience.

11. Conclusion

Cloud Shell’s browser experience crossed several powerful boundaries at once: authenticated web state, a bidirectional socket, arbitrary TCP forwarding, and an internal metadata service. The vulnerability appeared when each layer relied on another layer to decide whether a connection was safe.

The final exploit did not break TLS, steal the cookie with XSS, or compromise the Cloud Shell host first. It asked the victim’s browser to use a legitimate product feature from an illegitimate origin, and the system supplied the authentication and network reach needed to reach the token.

WebSockets require special care because correct user authentication during the initial HTTP request does not automatically make the upgraded connection safe. After the protocol upgrade, the server must still validate the initiating origin and enforce what the socket is authorized to reach. Otherwise, a properly authenticated connection can remain open to control from an untrusted website.

That is what made the chain dangerous, and what makes the lesson broadly applicable: browser-accessible tunnels must treat origin, session, and destination as one authorization decision.

GET A PERSONALIZED DEMO

Ready to see Cymulate in action?