gcp-spanner-exfiltrate-via-backup

Creating a Spanner backup (spanner.backups.create) then setting the backup's IAM policy (spanner.backups.setIamPolicy) to grant a cross-project principal spanner.backups.restoreDatabase copies the full database contents across the project boundary - effective exfiltration.

derived gcp emits CanExfiltrate

match (effective permission)

{ "action": "spanner.backups.create", "resource_type": "google.spanner.admin.instance.v1.Instance" }

where

?principal has EFFECTIVE spanner.databases.get on the source database (required to read the database for backup creation) ?principal has EFFECTIVE spanner.backups.setIamPolicy on the instance or project scope (applies to backups created within that scope) ?principal (or an identity they control in another project) can call spanner.databases.create and spanner.backups.restoreDatabase to restore the backup

emit

source typeIdentity
target typeRelationalDatabase
source<principal>
target<Spanner Database node>
permissionsspanner.backups.create spanner.backups.setIamPolicy
conditionsiam_permission resource_policy
state logicACTIVE when spanner.backups.create on the source database and spanner.backups.setIamPolicy on the instance or project scope are both confirmed ACTIVE and the attacker controls a restore target (another project with a database + spanner.backups.restoreDatabase grant). CONDITIONAL(iam_permission) when setIamPolicy on backups is uncertain (e.g., granted only at database scope but not instance/project scope, which may not propagate to a newly created backup). CONDITIONAL(resource_policy) when the cross-project restore principal is unknown or cannot be confirmed. POTENTIAL when the attacker is assumed to control an external project but it is not modeled. BLOCKED when VPC-SC perimeter blocks the Spanner API or when an org policy (e.g. constraints/gcp.resourceLocations) disallows backup creation in the required region.

Narrative

{principal.name} can create a backup of database {database.name} (spanner.backups.create) and set the backup's IAM policy (spanner.backups.setIamPolicy) to grant a cross-project principal restore access, copying the full database contents across the project boundary.

Raw rule rules/derived/gcp/spanner.yaml

id: gcp-spanner-exfiltrate-via-backup
emits: CanExfiltrate
description: "Creating a Spanner backup (spanner.backups.create) then setting the backup's IAM policy\
  \ (spanner.backups.setIamPolicy) to grant a cross-project principal spanner.backups.restoreDatabase\
  \ copies the full database contents across the project boundary \u2014 effective exfiltration."
match_effective_permission:
  action: spanner.backups.create
  resource_type: google.spanner.admin.instance.v1.Instance
where:
- ?principal has EFFECTIVE spanner.databases.get on the source database (required to read the database
  for backup creation)
- ?principal has EFFECTIVE spanner.backups.setIamPolicy on the instance or project scope (applies to backups
  created within that scope)
- ?principal (or an identity they control in another project) can call spanner.databases.create and spanner.backups.restoreDatabase
  to restore the backup
emit:
  source_type: Identity
  target_type: RelationalDatabase
  source: <principal>
  target: <Spanner Database node>
  permissions:
  - spanner.backups.create
  - spanner.backups.setIamPolicy
  conditions:
  - iam_permission
  - resource_policy
  state_logic: ACTIVE when spanner.backups.create on the source database and spanner.backups.setIamPolicy
    on the instance or project scope are both confirmed ACTIVE and the attacker controls a restore target
    (another project with a database + spanner.backups.restoreDatabase grant). CONDITIONAL(iam_permission)
    when setIamPolicy on backups is uncertain (e.g., granted only at database scope but not instance/project
    scope, which may not propagate to a newly created backup). CONDITIONAL(resource_policy) when the cross-project
    restore principal is unknown or cannot be confirmed. POTENTIAL when the attacker is assumed to control
    an external project but it is not modeled. BLOCKED when VPC-SC perimeter blocks the Spanner API or
    when an org policy (e.g. constraints/gcp.resourceLocations) disallows backup creation in the required
    region.
  false_positive_note: "CRITICAL: spanner.backups.create requires BOTH spanner.backups.create (on instance\
    \ scope) AND spanner.databases.get (on the source database). Without spanner.databases.get on the\
    \ source database, the CreateBackup API call fails with PERMISSION_DENIED. Roles are critical here:\
    \ roles/spanner.backupAdmin grants backups.create + backups.setIamPolicy but NOT databases.get. Only\
    \ roles/spanner.admin or roles/spanner.databaseAdmin grant databases.get. A principal with only backupAdmin\
    \ cannot create backups. Confirm the attacker holds ALL THREE permissions: spanner.backups.create\
    \ (instance), spanner.databases.get (database), and spanner.backups.setIamPolicy (instance or project).\
    \ spanner.backups.create also requires the source database is in state READY and the backup expiration\
    \ is within the database's version retention period. Without setIamPolicy on the backup, the attacker\
    \ can create the backup but cannot share it cross-project \u2014 downgrade to POTENTIAL(resource_policy)\
    \ in that case. The alternative exfiltration path (Dataflow export to GCS) is modeled in /rules/derived/gcp/dataflow.yaml\
    \ and cited there; do not duplicate it here. For CMEK-encrypted databases, the backup inherits CMEK\
    \ encryption \u2014 the restore target project must have access to the same CMEK key to decrypt the\
    \ restored database."
  narrative: '{principal.name} can create a backup of database {database.name} (spanner.backups.create)
    and set the backup''s IAM policy (spanner.backups.setIamPolicy) to grant a cross-project principal
    restore access, copying the full database contents across the project boundary.'
  derived_from:
  - spanner.backups.create effective permission on Instance scope
  - spanner.backups.setIamPolicy effective permission on Instance or Project scope
  - spanner.databases.get effective permission on the source Database
move · open · esc close