Skip to main content

Gitpod remote code execution 0-day vulnerability via WebSockets

Written by:
blog-feature-pypi-spoof

February 27, 2023

0 mins read

TLDR

This article walks us through a current Snyk Security Labs research project focusing on cloud based development environments (CDEs) — which resulted in a full workspace takeover on the Gitpod platform and extended to the user’s SCM account. The issues here have been responsibly disclosed to Gitpod and were resolved within a single working day!

Cloud development environments and Gitpod

As more and more companies begin to leverage cloud-based development environments for benefits such as improved performance, developer experience, consistent development environments, and low setup times, we couldn’t help but wonder about the security implications of adopting these cloud based IDEs.

First, let’s provide a brief overview of how CDEs operate so we can understand the difference between cloud-based and traditional, local-workstation based development — and how it changes the developer security landscape.

In contrast to traditional development, CDEs run on a cloud hosted machine with an IDE backend. This typically provides a web application version of the IDE and support for integrating with a locally installed IDE over SSH, giving users a seamless and familiar experience. When using a CDE, the organization’s code and any supporting services, such as a development database, are hosted within the cloud. Check out the following diagram for a visual representation of information flow in a CDE.

blog-websocket-takeover-cde-diagram

The security risks of locally installed development environments are not new. However, they historically haven’t received much attention from developers. In May 2021, Snyk disclosed vulnerable VS Code extensions that lead to a 1-click data leak or arbitrary command execution. These traditional workstation-based development environments, such as a local instance of VS Code or IntelliJ, carry other information security concerns — including hardware failure, data security, and malware. While these concerns can be addressed by employing Full Disk Encryption, version control, backups, and anti-malware systems, many questions remain unanswered with the adoption of cloud-based development environments: 

  • What happens if a cloud IDE workspace is infected with malware?

  • What happens when access controls are insufficient and allow cross-user or even cross-organization access to workspaces?

  • What happens when a rogue developer exfiltrates company intellectual property from a cloud-hosted machine outside the visibility of the organization's data loss prevention or endpoint security software?

In a current security research project here at Snyk, we examine the security implications of adopting cloud based IDEs. In this article we present a case study of one of the vulnerabilities discovered during our initial exploration in the Gitpod platform. 

Examining the Gitpod platform

Disclaimer: When it came to looking at cloud IDE solutions for our research, we settled on either self-hosted or cloud-based solutions, where the vendor has a clearly defined security policy providing safe harbor for researchers. 

One of the most popular CDE’s is Gitpod. Its wide adoption and extended feature set — including automated backups, Git integration out of the box, and multiple IDE backends — ensured that Gitpod was among the first products we looked into.

The first stage of our research involved becoming familiar with the basic workflows of Gitpod, setting up an organization, and experimenting with the product while capturing traffic using Burpsuite to observe the various APIs and transactions. We then pulled the Gitpod source code from GitHub to study the inner workings of its APIs, and reviewed any relevant architecture documentation to better understand each of the components and their function. A great resource for this was a video that provided an initial deep dive into the architecture of Gitpod. At a high level, Gitpod leverages multiple microservices deployed in a Kubernetes environment, where each user workspace is deployed to a dedicated ephemeral pod. 

Gitpod’s primary set of external components are concerned with the dashboard, authentication, and the creation and management of workspaces, organizations, and accounts. At its core, the main component here is aptly named server, a TypeScript application that exposes a JSONRPC API over WebSocket that is consumed by a React frontend called dashboard

From the dashboard, it’s easy to integrate with a SCM provider, such as GitHub or Bitbucket, to import a repository and spin up a development environment — which then serves the source code and provides a working Git environment. Once the workspace is provisioned, it is made accessible via SSH and HTTPS on a subdomain of gitpod.io (i.e https://[WORKSPACE_NAME].[CLUSTER_NAME].gitpod.io) through a Golang-based component called ws-proxy

The security vulnerability that was discovered through our research relates primarily to the server component and the JSONRPC served over a WebSocket connection, which ultimately led to a workspace takeover in Gitpod.

Technical details

WebSockets and Same Origin Policy

WebSocket is a technology that allows for real-time, two-way communication between a client (typically a web browser) and a server. It enables a persistent connection between the client and server, allowing for continuous “real-time” data transfer without the need for repeated HTTP requests. 

An interesting aspect of WebSockets from a security perspective, is that a browser security mechanism, the Same Origin Policy (SOP), does not apply. This is the security control which prevents a website from issuing an AJAX request to another website and being able to read the response. If this were possible, it would present a security concern because browsers typically submit cookies along with every request (even for Cross Origin requests, such as CSRF related attacks). Without SOP, any website would be able to issue requests to foreign websites and obtain your data from other domains.

This leads us to the vulnerability class known as Cross-Site WebSocket Hijacking. This attack is similar to a combination of a Cross-Site Request Forgery and CORS misconfiguration. When a WebSocket handshake relies solely upon HTTP cookies for authentication, a malicious website is able to instantiate a new WebSocket connection to the vulnerable application, allowing an attacker to both send and receive data through the connection.

When reviewing an application with WebSocket connections, it’s always worth examining this in depth. Let’s take a look at the WebSocket request for the Gitpod server.

blog-websocket-takeover-gitpod.io-upgrade
blog-websocket-takeover-switching-protocols-feb-9

In normal circumstances, the connection is successfully upgraded to a WebSocket and communication begins. There was no additional authentication taking place within the WebSocket exchange itself, and the JSONRPC can be invoked via the WebSocket connection.

blog-websocket-takeover-jsonrpc-2.0

So far, we’ve found no additional authentication taking place within the established channel — a good sign for any potential attackers. Now, let's verify that no additional Origin checks are taking place by taking a handshake we have observed and tampering with the Origin header.

blog-websocket-takeover-origin-workspace-id
blog-websocket-takeover-switching-protocols-feb-12

This looks promising! It seems that the domain evil.com is able to issue Cross-Origin WebSocket requests to gitpod.io. However, another security mechanism introduces a challenge.

SameSite Cookie bypass

SameSite cookies are a fairly recent addition, providing partial mitigation against Cross-Site Request Forgery (CSRF) attacks. While not everyone has adopted them, most popular browsers have made the default value for all cookies which do not explicitly disable SameSite to be Lax. So while the underlying vulnerability is present, without a bypass for SameSite cookies our attack would largely be theoretical and only work against a subset of outdated and niche browsers.

So what is a site in the context of SameSite? Simply put, the site corresponds to the combination of the scheme and the registrable domain (if any) of the origin’s host. If we look at the specifications for SameSite we can see that subdomains are not considered. This is more relaxed than the specification of an Origin used by the Same Origin Policy, which is comprised of a scheme + host (including subdomains) + port (eg: https://security.snyk.io:8443).

blog-websocket-takeover-url-breakdown

Earlier, we observed that the workspace was exposed via a subdomain on gitpod.io. In the context of SameSite, the workspace URL is considered to be the same site as gitpod.io. So, it should be possible for one workspace to issue a cross-domain SameSite request to a *.gitpod.io domain with the original user’s cookies attached. Let’s see if we can leverage an attacker controlled workspace to serve a WebSocket Hijacking payload. 

To first verify that the cookies are indeed transmitted and the WebSocket communication is successfully achieved, let's open the browser console from a workspace and attempt to initiate a WebSocket connection.

blog-websocket-takeover-gitpod-outline-cropped

As we can see in the above screenshot, we attempt to open a new WebSocket connection and, once open, submit a JSONRPC request. We can see in the console output that a message has been received containing the result of our request, confirming that the cookies are submitted and the origin is permitted to open a websocket to gitpod.io.

We now need a way to serve JavaScript from the workspace that can be accessed by a Gitpod user. When looking through the features available, it is possible to expose ports in the workspace and make them accessible using a command line utility called gitpod-cli — available on the path inside the workspace by typing gp. We can invoke gp ports expose 8080, and then set up a basic Python web server using python -m http.server 8080. This in turn creates a new subdomain where the exposed port can be accessed as shown below.

blog-websocket-takeover-consol-error

However, the connection failed. This is somewhat concerning and requires more investigation. Here we open the source code and start looking for what could be causing the problem. We found the following regular expression pattern, which appears to be used to extract the workspace name from the URL.

blog-websocket-takeover-base-workspace-id

The way this matching is performed results in the wrong workspace name being extracted, as demonstrated in the following screenshot:

blog-websocket-takeover-test-string

So, it looks like we can’t serve our content from an exposed port inside the Gitpod hosted workspace and we need another way. By now we already know that we have privileged access to a machine that's running the VS Code service and is serving requests issued to our workspace URL — so can we abuse this in some way?

The initial idea was to terminate the vscode process and start a Python web server to serve an HTML file. Unfortunately, this did not work and resulted in the workspace being restarted. This appeared to be performed by a local service supervisor. While testing this approach we noticed that when we terminated the process without binding another process to the VS Code port, the supervisor service will automatically restart the vscode process, resulting in a brief hang to the UI without a full restart of the workspace.

This brought a promising idea. Can we patch VS Code to serve a built-in exploit for us?

Patching VS Code was relatively easy. By comparing the original VS Code server source code to the distributed version, we quickly found a convenient location to serve the exploit.

VS Code contains an API endpoint at /version, which returns the commit of the current version:

blog-websocket-takeover-pathname-version

We modified it so that the correct Content-Type of text/html and the contents of an HTML file were returned. Now, we terminated the vscode process, allowing our newly introduced changes to load into a newly spawned VS Code process instance:

blog-websocket-takeover-template-python

Finally, we can leverage the JSONRPC methods getLoggedInUser, getGitpodTokens, getOwnerToken, and addSSHPublicKey to build a payload that grants us full control over the user’s workspaces when an unsuspecting Gitpod user visits our link!

Here it is in action:

blog-websocket-takeover-pwnpod-info

We can see that we’ve been able to extract some sensitive information about the user account, and are notified that our SSH key has been added to the account. Let's see if we can SSH to the workspace:

blog-websocket-takeover-task-list-redacted

Mission successful! As shown above, we have full access to the user’s workspaces after they’ve visited a link we sent them!

Timeline

  • Mon, Feb. 13, 2023 - Vulnerability disclosed to vendor

  • Mon, Feb. 13, 2023 - Vendor acknowledges vulnerability

  • Tue, Feb. 14, 2023 - New version released and deployed to production SaaS Gitpod instance

  • Tue, Feb. 22, 2023 - CVE-2023-0957 assigned

  • Wed, Mar. 1, 2023 - Vendor releases new version for Gitpod Self-Hosted and issues advisory

Summary

In this post, we presented the first findings from our current research into Cloud Development Environments (CDEs) — which allowed a full account takeover through visiting a link, exploiting a commonly misunderstood vulnerability (WebSocket Hijacking), and leveraging a practical SameSite cookie bypass. As cloud developer workspaces are becoming increasingly popular, it's important to consider the additional risks that are introduced.

We would like to praise Gitpod for their fantastic turnaround on addressing this security vulnerability, and look forward to presenting more of our findings on cloud-based remote development solutions in the near future.

blog-feature-pypi-spoof

How to Build a Security Champions Program

Snyk interviewed 20+ security leaders who have successfully and unsuccessfully built security champions programs. Check out this playbook to learn how to run an effective developer-focused security champions program.