An Introduction to GCPwn – Part 1
GCPwn is a python-based framework for pentesting GCP environments. While individual exploit scripts exist today for GCP attack vectors, GCPwn seeks to consolidate all these scripts and manage multiple sets of credentials at once (for example, multiple service account keys) all within one framework. With the use of interactive prompts, GCPwn makes enumeration and exploitation of resources/permissions more trivial to execute aiding the average pentester. The tool also tries to use the newer GCP python SDK as opposed to the older libraries. The idea of a python framework along with the overall presentation builds upon the concepts of Rhino Security’s Pacu tool, which is a python framework for testing AWS.
GCPwn has the following high-level traits:
- Accepts/manages GCP credentials of different types
- Packages together enumeration/exploit scripts for different services for quick execution.
- Tracks permissions passively and allows one to brute force permissions through multiple testIamPermissions calls.
- Presents a framework for the research community to build upon using Google’s newest python SDKs.
This blog is broken out into 3 parts as follows:
- Part 1: Cover the core concepts and high-level steps required to use the tool
- Part 2 & 3 (will be released at a later date): Walk through example enumeration and exploitation scenarios using GCP in a test environment. Includes Cloud Storage HMAC keys, Cloud Functions metadata endpoint exploit, and IAM implicit delegation.
As a disclaimer, the tool is changing over time as I work on it. To see the most up-to-date information check out the GCPwn wiki. The tool also has been presented at fwd:cloudsec 2024.
Step 0: Installation
GCPwn can be installed either through a simple setup script, or via docker. Installation instructions for both methods are covered here. In the local installation, git clone the repository, run “setup.sh”, and start GCPwn with “python3 main.py”. For docker, build the image using the Dockerfile and mount the desired folders at run time to save any data collected while running the tool.
Step 1: Adding Credentials to Tool
In most cases, you will need to add credentials to GCPwn to subsequently launch modules as that user or service account. GCPwn supports ADC credentials, standalone OAuth2 tokens, and service account JSON key files. GCPwn also supports a couple unauthenticated modules at this point in time. More details for each method are given below:
- Application Default Credentials (ADC): This terminology will probably change in the future as “ADC” is more applicable to what order GCP fetches credentials, but for now this flow is tied to email/password submissions for simplicity. This path usually involves running a series of “gcloud” (the GCP command line utility) commands. These will allow you to authenticate in a web browser with the username/password which in turn will generate a refresh token and OAuth2 tokens in the background. While the OAuth2 tokens for ADC credentials usually expire, GCPwn will attempt to auto-refresh credentials via the refresh token when resuming the session.
Example Credentials
Email: <email> Password: <password>
Set up ADC Credentials Before Launching Tool
gcloud auth login gcloud config project set <project_id> gcloud auth application-default login
Add ADC Credentials to GCPwn
Input: adc leaked_adc_dev_creds [*] Project ID of credentials is: my-private-test-project-430102 [*] Credentials successfully 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 successfully refreshed... [*] Credentials successfully 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)>
- Standalone OAuth2 Token: This flow is for valid GCP OAuth2 tokens without corresponding refresh tokens. An example scenario might be getting a service account OAuth2 access token via the GCP metadata endpoint. You have the access token, but there is no corresponding refresh token like the “ADC” route. An OAuth2 token by itself will usually be valid for X amount of time before expiring. Without a refresh token, GCPwn won’t be able to auto-refresh the OAuth2 access token. However, you can update existing credentials with “creds update” if you swap out the expired OAuth2 token with a new valid OAuth2 token. You might also have to manually set the project ID via “projects set” as shown below.
Example Credentials
OAuth2 Token: ya29.a0AXooC[REDACTED]
Add OAuth2 Credentials to GCPwn
Input: oauth2 webbinroot_oauth2_token ya29.a0AXooC[REDACTED] [*] Project ID of credentials is: Unknown [*] The project associated with these creds is unknown. To bind the creds to a project specify "creds <credname> set <projectname>". Otherwise you might have limited functionality with resources. [*] Loading in our OAuth2 credentials... (Unknown:webbinroot_oauth2_token)> projects set my-private-test-project-430102 [X] my-private-test-project-430102 is not in the list of project_ids. Adding... (my-private-test-project-430102 :webbinroot_oauth2_token)> projects
- Service Account Keys: This flow is for valid GCP service account keys in the exported JSON format (you could also just build the JSON if you had all the corresponding info). These service account keys are pretty well known and static. At the moment, these don’t have any mechanisms in GCPwn for auto-refreshing as the expectation is their lifetime would not make it necessary.
Example Credentials
{ "type": "service_account", "project_id": "[Project_ID]", "private_key_id": "[private_key_id]", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEv[TRUNCATED]\n-----END PRIVATE KEY-----\n", "client_email": "[client_email]", "client_id": "[client_id]", "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/tes[TRUNCATED]", "universe_domain": "googleapis.com" }
Add Service Account Credentials to GCPwn
Input: service webbinroot_service_key /home/kali/Downloads/my_service_key.json Loading in our Service credentials... (my-private-test-project-430102 :webbinroot_service_key)>
- No Credentials: This flow is for when no credentials are needed. Entering nothing at the credential prompt drops the user into an unauthenticated session context. For example, GCPBucketBrute by Rhino Security was added as a cloud storage unauthenticated module and does not require credentials to run.
Proceed With No Credentials in GCPwn
Input: (None:None)>
As seen above, each prompt within GCPwn includes the default project ID followed by the name tag for the current credential set. For many modules, especially those in the “enumeration” category, this is the default project ID that the tool would fall back to. To override the default project, you can pass in the following flag with modules: --project-ids <project_id1>,<project_id2>,…
. As will be seen in later parts of this blog, if multiple project IDs are known, GCPwn will prompt the user if they want to run modules in the current default project ID or all known project IDs.
A couple quick final notes about the credentials include:
- The tool saves credentials between runs so resuming the tool (running “python3 main.py”) should allow to resume your credentials context. Note certain credentials like standalone OAuth2 tokens might expire based on the time between runs.
- You can add credentials from within in the tool with
creds add
as opposed to having run to run “python3 main.py” each time. - You can swap between credentials you have saved within one workspace with
creds swap
. - You can update credentials via
creds update
. For example, if your OAuth2 token expires and you get a new standalone token, you can runcreds update
with the flags dictated by the help menu to swap out your expired OAuth2 token with the new one. - You can see all permissions and other “whoami” information about your creds thus far with
creds info
. This command will probably be run a lot, andcreds info –csv
saves the summary to a CSV file in “GatheredData” folder ( helpful when dealing with potentially thousands of permissions). - You can use
tokeninfo
when adding credentials or from within the current credential set to send the current OAuth2 token to Google’s official “tokeninfo” endpoint detailed here. - You can manually set your current project with
projects set <project_id>
in the tool. This is useful if you manually want to add a project (maybe discovered through recon) to launch modules in, or if the tool is a bit buggy and didn’t add the project ID when adding credentials.
Step 2: Picking An Enumeration/Exploit/Process/Unauthenticated Module
With the target credentials loaded, it’s now time to put them to work by running modules. Modules in GCPwn are snippets of python code made to achieve certain tasks. Their self-contained nature makes it ideal for open-source contributions in the future (current wiki on this topic is in-progress).
When in GCPwn you can run a module via modules run <module_name>
. As of today, all current/upcoming modules are shown below.
Modules are broken out into Enumeration, Exploit, Process, and Unauthenticated, and categories usually within a respective service (the “Everything” category being the exception). Note most of these are covered in detail here.
Enumeration Modules
Enumerate and download/exfiltrate data identified for the specified service. All the data enumerated is stored in GCPwn’s internal SQLite databases which can then be accessed by later exploit modules to make crafting attack vectors easier. By default, most modules when supplied with no flags just enumerate the service metadata (no downloads or testIamPermissions calls). They do so by generally making “List” API calls for the select service followed by “Get” API calls for each entity found.
Most enumeration modules support one of the following common flags:
--iam
: If the service entity supports allow policies (ex. organizations, folders, projects, cloud functions, buckets, compute instances, etc.), then the enumeration module will run testIamPermissions for the given asset. While I won’t dive into the testIamPermissions API here, I did write something up on hackingthe.cloud if that’s something you wanted to review. In short, testIamPermissions allows you to pass in a list of permissions, and the response is a list of permissions you are allowed to call.--download
: Exfiltrate/download data to the local filesystem in the GatheredData folder at the root of GCPwn. Runningmodules run enum_buckets --download
, for example, will try to download all blobs enumerated in GCP. As another example, runningmodules run enum_secrets --download
will try downloading all the secret version values.--minimal-calls
: If you want to ONLY call the “List” APIs and not the “Get” APIs.--[resource-name] [resource_name_format]
: Specify one or more specific resources. This is useful if you do not have “List” permissions for the given service, but you still know the specific resource name to target. resource_name_format can usually be found by running the-h
flag for a given module to see the help menu.
Besides these common flags, each service module usually has its own specific flags. Two examples are provided below:
--good-regex
: A flag within “enum_buckets” to filter downloads based off python regex. For example, you could use it to target only files ending in a certain extension (ex.modules run enum_buckets --iam --download --good-regex "\.sh”
)--version-range
: A flag within “enum_secrets” that defines a range of integers to check in terms of secret versions including the keyword “latest”. For example, you could use it to brute force secret versions if you know the secret name but can’t list anything specific (ex.modules run enum_secrets --secrets projects/[project_id]/secrets/test --version-range 1-99,latest –download
)
Finally, two noticeable enumeration modules to mention are “enum_all” and “enum_policy_bindings”.
- “enum_all”: Accepts the common
--iam
,--download
, etc. flags and will run ALL enumeration modules for you so you don’t have to run each of them individually. Is probably your go-to in most engagements. - “enum_policy_bindings”: Gathers all IAM policies for all resources gathered thus far to be used in later IAM analysis if needed. This is a pre-requisite for “process_iam_bindings” which returns a summary of IAM roles per user.
Exploit Modules
Exploit modules are more focused on privilege escalation or pivoting techniques rather than enumerating data. Ideally, most exploit modules can run with ZERO flags and GCPwn will reference any enumerated data thus far to walk you through a “wizard” of sorts. You can also pass in all the flags manually if you want. Among the exploit modules included are:
- exploit_generate_access_token: Generate an access token for a given service account
- exploit_generate_service_key: Generate a JSON service key for a given service account
- exploit_functions_invoke: Create or update a cloud function and subsequently invoke it to pull back the corresponding access tokens for the attached service account.
- exploit_[service]_setiampolicy: exploit setIamPolicy on the target resource, usually to set yourself as some type of admin over the resource
Many of these exploit scripts were based off of rhino security’s research in the area.
In terms of the “wizard” walkthrough or exploit scripts, a demo is shown below for setIamPolicy for cloud storage buckets. Note that “enum_buckets” was run beforehand, so when you launch “exploit_storage_setiampolicy” with no flags, the tool will reference the buckets enumerated earlier and prompt the user too choose one for exploitation.
(my-private-test-project-430102:test_blog)> modules run exploit_storage_setiampolicy > Choose an existing bucket from below to edit the corresponding policy: >> [1] bucket-[TRUNCATED] >> [2] gcf-v2-sources-[TRUNCATED] >> [3] gcf-v2-uploads-[TRUNCATED] >> [4] testw[TRUNCATED] > [5] Exit > Choose an option: 2 > Do you want to use newserviceaccount@my-private-test-project-430102.iam.gserviceaccount.com set on the session? [y/n]n > Do you want to use an enumerated SA/User or enter a new email? >> [1] Existing SA/User >> [2] New Member > [3] Exit > Choose an option: 2 > Provide the member account email below in the format user:<email> or serviceAccount:<email>: user: [REDACTED]@gmail.com > A list of roles are supplied below. Choose one or enter your own: >> [1] roles/storage.admin (Default) [TRUNCATED] > [12] Exit > Choose an option: 1 [*] Binding Member user: [REDACTED]@gmail.com on gcf-v2-sources-[TRUNCATED] to role roles/storage.admin [*] Fetching current policy for gcf-v2-sources-[TRUNCATED]... [*] New policy below being added to gcf-v2-sources-[TRUNCATED] [[TRUNCATED], {'role': 'roles/storage.admin', 'members': ['user:[REDACTED]@gmail.com']}] [*] Successfully added user:[REDACTED]@gmail.com to the policy of bucket gcf-v2-sources-[TRUNCATED]
Unauthenticated Modules
Unauthenticated modules can be run without any credentials or project set in GCPwn. Only a few exist at this point in time. This mainly encapsulates GCPBucketBrute which is included as “unauth_bucketbrute” and works the same as the standalone tool.
Process Modules
Process modules can be run offline and are mainly for ingesting IAM data for analyzing/presenting summaries and vulnerabilities.
- “process_iam_bindings”: Generate an IAM summary report of all IAM policy bindings pulled thus far. This includes inherited permissions and custom roles/convenience roles assuming the caller has the permissions to resolve these scenarios. It should be noted that GCPwn will report inherited permissions wherever a user is attached to a resource. So, if User A has role/owner permissions at the organization level, and User A has roles/viewer permissions at the project level, the tool will tell you User A at the project level inherited role/owner from the organization but WON’T necessarily tell you User A inherited role/owner on bucket ABC within the project. But that’s something you can easily infer.
- “analyze_vulns”: Based off the results of “process_iam_bindings”, flag any users or service accounts with either single or group permissions or roles that would be deemed dangerous or risky. This is still in progress but currently provides a TXT/CSV with those users/roles with “dangerous” permissions. Also, it will highlight all policies with allUsers or allAuthenticatedUsers members identified.
Unauthenticated Modules
- Unauthenticated modules can be run without any credentials or project set in GCPwn. Only a few exist at this point in time. This mainly encapsulates GCPBucketBrute which is included as “unauth_bucketbrute” and works the same as the standalone tool.
Step 3: Checking Permissions
Permissions are an important aspect of GCPwn when you begin with credentials in an unknown environment. One of the first questions you usually ask is “what permission do I have/what can I do?” Individual granular permissions are summarized per credential set via the `creds info` command, and roles are summarized through the output of “process_iam_bindings”. Permissions and roles are mainly populated by:
- Module runs: As you run all the modules in the background, GCPwn is noting all the permissions tied to successful API calls. For example, if you are able to list all the buckets when you run “enum_buckets”, than GCPwn will note you have the storage.buckets.list permissions within the current project which will be visible next time you run
creds info
. - testIamPermissions: As discussed earlier, testIamPermissions is an API in GCP that will take in a large list of permissions and return those permissions the caller has over the given resource. Interestingly, the API to return all permissions does not itself have a permission to invoke meaning its fairly accessible as seen by the details for the project-level version:“There are no permissions required for making this API call”. Another nice feature of testIamPermissions is that it allows one to pass in a large set of permissions in one API call making it very effective in permission enumeration allowing up to ~9500 permissions in batches (link to that in “Final Notes”). As stated before, the
--iam
flag in enumeration modules invokes testIamPermissions at the specified resource level, and GCPwn will save all those testIamPermissions responses which are visible the next time you runcreds info
. The TLDR, add –iam to enumeration modules and you don’t have to manually manage it 🙂 - policy bindings: While 1 & 2 above deal with granular permissions, GCP predefined roles are collected when running “enum_policy_bindings”. This module will grab all the policy bindings for resources enumerated thus far which can then be run through “process_iam_bindings” to produce an IAM summary report. This would notably include the “roles” for the users/service accounts as opposed to the granular “permissions” and might be easier to read/parse.
The command, creds info
will probably be run a lot through the course of your pentest. An example is shown below of running creds info
before and after executing the enum_buckets module. Note how fresh credentials have no permissions but the successful enum_buckets execution added permissions in the background.
Before Running Any Modules
(production-project-1-426001:test_blog)> creds info Summary for test_blog: Email: newserviceaccount@production-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: production-project-1-426001 All Projects: - [TRUNCATED] Access Token: N/A
After Running “enum_buckets –iam”
(production-project-1-426001:test_blog)> creds info Summary for test_blog: Email: newserviceaccount@production-project-1-426001.iam.gserviceaccount.com Scopes: - N/A Default Project: production-project-1-426001 All Projects: - production-project-1-426001 Access Token: N/A [******] Permission Summary for test_blog [******] - Project Permissions - production-project[TRUNCATED] - storage.buckets.list - Storage Actions Allowed Permissions - production-project[TRUNCATED] - storage.buckets.get - gcf-v2-sources-[TRUNCATED] (buckets) - gcf-v2-uploads-[TRUNCATED] (buckets) - test[TRUNCATED] (buckets) - storage.objects.list - gcf-v2-sources-[TRUNCATED] (buckets) - storage.objects.get - gcf-v2-sources-[TRUNCATED] (buckets)
Final Notes; TLDR
No doubt the data above + the wiki is a lot to digest. I think most people’s use cases will fall into 2 broad scenarios which I’ve outlined it in the wiki here. Most notably, my favorite scenario is how to brute force ~9500 permissions at the project level with testIamPermissions which I think is pretty cool 😊
Explore more blog posts
Bytes, Books, and Blockbusters: The NetSPI Agents’ Top Cybersecurity Fiction Picks
Craving a cybersecurity movie marathon? Get recommendations from The NetSPI Agents on their favorite media to get inspired for ethical hacking.
Social Engineering Stories: One Phish, Two Vish, and Tips for Stronger Defenses
Hear real-world social engineering stories from The NetSPI Agents and tips to enhance your social engineering testing.
Hacking CICS: 7 Ways to Defeat Mainframe Applications
Explore how modern penetration testing tools uncover vulnerabilities in mainframe applications, highlighting the need for methodical techniques and regular testing to protect these critical systems from threats.