gcp-pd-snapshot-create-and-share

Create a snapshot of a disk and grant a cross-project principal access to it, enabling the attacker to restore it as a disk in their own project and read all on-disk data (secrets, credentials, database files).

derived gcp emits CanExfiltrate

match (effective permission)

[ { "action": "compute.snapshots.create", "resource_type": "compute.googleapis.com/Disk" }, { "action": "compute.snapshots.setIamPolicy", "resource_type": "compute.googleapis.com/Snapshot" } ]

where

?principal has EFFECTIVE BOTH compute.snapshots.create on the disk AND compute.snapshots.setIamPolicy on the resulting snapshot (the latter held at project scope, which grants it on snapshots created in that project) ?principal (or an identity they control in another project) has compute.disks.create and compute.snapshots.useReadOnly to restore the snapshot

emit

source typeIdentity
target typeSnapshot
source<principal>
target<disk data via created snapshot>
permissionscompute.snapshots.create compute.snapshots.setIamPolicy compute.disks.create compute.snapshots.useReadOnly
conditionsiam_permission region_or_boundary
state logicCONDITIONAL(iam_permission, region_or_boundary) by default: evidence of compute.snapshots.create + setIamPolicy in source project is confirmed, but destination-project permissions (compute.disks.create + useReadOnly) are typically unobservable from victim-side collection; upgrade to ACTIVE only when the attacker's project permissions are confirmed (via cross-project collection or org-scope Asset Inventory). BLOCKED if an Org Policy (constraints/compute.storageResourceUseRestrictions) prevents cross-project snapshot sharing, or if the disk is CSEK-encrypted (key not accessible via IAM). If disk is CMEK-encrypted (diskEncryptionKey.kmsKeyName present), emit as CONDITIONAL(key_permission) in addition to the above — cite decrypt-gates-secret-read in /rules/derived/credential-chains.yaml for the KMS gate resolution.

Narrative

{principal.name} can create a snapshot of {disk.name} (compute.snapshots.create) and grant a principal in another project access to it (compute.snapshots.setIamPolicy), enabling full read of all on-disk data - including secrets, credentials, and database files - by restoring the snapshot as a new disk (compute.snapshots.useReadOnly + compute.disks.create).

Raw rule rules/derived/gcp/pd.yaml

id: gcp-pd-snapshot-create-and-share
emits: CanExfiltrate
description: Create a snapshot of a disk and grant a cross-project principal access to it, enabling the
  attacker to restore it as a disk in their own project and read all on-disk data (secrets, credentials,
  database files).
applies_to:
- gcp
match_effective_permission:
- action: compute.snapshots.create
  resource_type: compute.googleapis.com/Disk
- action: compute.snapshots.setIamPolicy
  resource_type: compute.googleapis.com/Snapshot
where:
- ?principal has EFFECTIVE BOTH compute.snapshots.create on the disk AND compute.snapshots.setIamPolicy
  on the resulting snapshot (the latter held at project scope, which grants it on snapshots created in
  that project)
- ?principal (or an identity they control in another project) has compute.disks.create and compute.snapshots.useReadOnly
  to restore the snapshot
emit:
  source_type: Identity
  target_type: Snapshot
  source: <principal>
  target: <disk data via created snapshot>
  permissions:
  - compute.snapshots.create
  - compute.snapshots.setIamPolicy
  - compute.disks.create
  - compute.snapshots.useReadOnly
  conditions:
  - iam_permission
  - region_or_boundary
  state_logic: "CONDITIONAL(iam_permission, region_or_boundary) by default: evidence of compute.snapshots.create\
    \ + setIamPolicy in source project is confirmed, but destination-project permissions (compute.disks.create\
    \ + useReadOnly) are typically unobservable from victim-side collection; upgrade to ACTIVE only when\
    \ the attacker's project permissions are confirmed (via cross-project collection or org-scope Asset\
    \ Inventory). BLOCKED if an Org Policy (constraints/compute.storageResourceUseRestrictions) prevents\
    \ cross-project snapshot sharing, or if the disk is CSEK-encrypted (key not accessible via IAM). If\
    \ disk is CMEK-encrypted (diskEncryptionKey.kmsKeyName present), emit as CONDITIONAL(key_permission)\
    \ in addition to the above \u2014 cite decrypt-gates-secret-read in /rules/derived/credential-chains.yaml\
    \ for the KMS gate resolution."
  false_positive_note: "Same-project snapshotting without an external IAM grant is NOT exfiltration. Emit\
    \ CrossProjectTrust and CanExfiltrate only when there is evidence that the snapshot is or will be\
    \ shared with a principal outside the source project boundary. compute.snapshots.create alone without\
    \ compute.snapshots.setIamPolicy is insufficient \u2014 the snapshot must be accessible from the attacker's\
    \ project. CMEK-encrypted disks: a snapshot inherits the disk's CMEK binding; compute.disks.create\
    \ from such a snapshot requires cloudkms.cryptoKeyVersions.useToDecrypt on the wrapping key \u2014\
    \ downgrade to CONDITIONAL(key_permission) until the decrypt-gates-secret-read linchpin resolves it.\
    \ CSEK-encrypted disks: the caller must supply the raw 256-bit key in the API request; this is not\
    \ expressible as a GCP IAM permission path \u2014 emit BLOCKED. Org Policy constraints/compute.storageResourceUseRestrictions\
    \ may restrict the destination projects allowed; honor it as BLOCKED when in place."
  derived_from:
  - <compute.snapshots.create effective permission on source disk>
  - <compute.snapshots.setIamPolicy effective permission on created snapshot>
  - <cross-project compute.snapshots.useReadOnly + compute.disks.create grant>
  narrative: "{principal.name} can create a snapshot of {disk.name} (compute.snapshots.create) and grant\
    \ a principal in another project access to it (compute.snapshots.setIamPolicy), enabling full read\
    \ of all on-disk data \u2014 including secrets, credentials, and database files \u2014 by restoring\
    \ the snapshot as a new disk (compute.snapshots.useReadOnly + compute.disks.create)."
move · open · esc close