gcp-bigquery-exfiltrate
An EXPORT DATA statement or bq extract job copies BigQuery table contents to a GCS bucket - effective data exfiltration across the project boundary.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?principal has EFFECTIVE bigquery.tables.export on the dataset/tables (required for EXPORT DATA / bq extract)
?principal has EFFECTIVE storage.objects.create on a destination GCS bucket (attacker-controlled or accessible)
emit
| source type | Identity |
|---|---|
| target type | Data |
| source | ?principal |
| target | ?dataset |
| permissions | bigquery.tables.getData bigquery.jobs.create bigquery.tables.export storage.objects.create |
| conditions | iam_permission resource_policy |
| state logic | ACTIVE when CanReadData(?principal, ?dataset) is ACTIVE AND bigquery.tables.export is confirmed ACTIVE AND storage.objects.create on a writable destination bucket is confirmed; CONDITIONAL(iam_permission) when bigquery.tables.export is uncertain or bucket write rights are uncertain; BLOCKED when tables.export is explicitly denied. |
Narrative
{principal.name} can submit an EXPORT DATA job to copy tables from {dataset.name} to a GCS bucket it controls (bigquery.tables.getData + bigquery.jobs.create + bigquery.tables.export + storage.objects.create), exfiltrating data outside the project boundary.
Raw rule rules/derived/gcp/bigquery.yaml
id: gcp-bigquery-exfiltrate
emits: CanExfiltrate
description: "An EXPORT DATA statement or bq extract job copies BigQuery table contents to a GCS bucket\
\ \u2014 effective data exfiltration across the project boundary."
match:
- - principal: null
- CanReadData
- dataset: null
where:
- ?principal has EFFECTIVE bigquery.tables.export on the dataset/tables (required for EXPORT DATA / bq
extract)
- ?principal has EFFECTIVE storage.objects.create on a destination GCS bucket (attacker-controlled or
accessible)
emit:
source_type: Identity
target_type: Data
source: ?principal
target: ?dataset
permissions:
- bigquery.tables.getData
- bigquery.jobs.create
- bigquery.tables.export
- storage.objects.create
conditions:
- iam_permission
- resource_policy
state_logic: ACTIVE when CanReadData(?principal, ?dataset) is ACTIVE AND bigquery.tables.export is confirmed
ACTIVE AND storage.objects.create on a writable destination bucket is confirmed; CONDITIONAL(iam_permission)
when bigquery.tables.export is uncertain or bucket write rights are uncertain; BLOCKED when tables.export
is explicitly denied.
false_positive_note: "EXPORT DATA / bq extract requires THREE permissions on the principal's identity:\
\ (1) bigquery.tables.getData (read the table data), (2) bigquery.jobs.create (submit the export job),\
\ (3) bigquery.tables.export (execute EXPORT DATA / extract \u2014 NOT included in roles/bigquery.dataViewer,\
\ only in roles/bigquery.dataEditor and higher). Additionally, storage.objects.create is required\
\ on the DESTINATION bucket. A principal with roles/bigquery.dataViewer + bigquery.jobUser satisfies\
\ CanReadData but CANNOT export without tables.export. Without bucket write rights the export fails\
\ (CanExfiltrate is CONDITIONAL(iam_permission) on that right). The destination can be any GCS bucket\
\ the principal can write to \u2014 including buckets in other projects or owned by the attacker.\
\ VPC-SC perimeters can restrict cross-perimeter GCS writes (bigquery + GCS must be co-perimeterized\
\ or the export blocked). Authorized views do NOT grant EXPORT capability \u2014 a view reader cannot\
\ export the underlying base table unless they also hold getData + tables.export on it. Only emit\
\ when the source node is a DataWarehouse or its containing dataset."
narrative: '{principal.name} can submit an EXPORT DATA job to copy tables from {dataset.name} to a GCS
bucket it controls (bigquery.tables.getData + bigquery.jobs.create + bigquery.tables.export + storage.objects.create),
exfiltrating data outside the project boundary.'
derived_from:
- ?principal CanReadData ?dataset
- bigquery.tables.export effective permission
- storage.objects.create on destination bucket