gcp-datastream-stream-update-exfil
datastream.streams.update lets a principal redirect the stream's destination (BigQuery dataset or GCS path) to an attacker-controlled sink, exfiltrating all CDC data from the source database.
match (effective permission)
{
"action": "datastream.streams.update",
"resource_type": "google.datastream.Stream"
}
emit
| source type | Identity |
|---|---|
| target type | Storage |
| source | <principal> |
| target | <attacker-controlled destination: ObjectStorage (GCS bucket) or DataWarehouse (BigQuery dataset)> |
| permissions | datastream.streams.update |
| conditions | iam_permission api_enabled |
| state logic | ACTIVE if datastream.streams.update is confirmed in the effective grant and the principal can also create a destination connection profile pointing to an attacker-controlled sink (requires datastream.connectionProfiles.create or access to an existing attacker-controlled profile). If the principal can only update the stream (not create new profiles), the attack is CONDITIONAL on an existing attacker-controlled connection profile being available. BLOCKED if a deny policy applies to datastream.streams.update or if a VPC Service Controls perimeter blocks the API call. Note: stopping and restarting the stream (streams.start/stop) triggers a backfill — a FULL copy of the source database to the new destination. |
Narrative
{principal.name} can update the Datastream stream {stream.name} (datastream.streams.update) to replace its destination configuration with an attacker-controlled BigQuery dataset or GCS bucket; ongoing CDC data and any triggered backfill (full historical copy of {stream.name}'s source tables) will be written to the attacker-controlled destination.
Raw rule rules/derived/gcp/datastream.yaml
id: gcp-datastream-stream-update-exfil
emits: CanExfiltrate
description: datastream.streams.update lets a principal redirect the stream's destination (BigQuery dataset
or GCS path) to an attacker-controlled sink, exfiltrating all CDC data from the source database.
match_effective_permission:
action: datastream.streams.update
resource_type: google.datastream.Stream
emit:
source_type: Identity
target_type: Storage
source: <principal>
target: '<attacker-controlled destination: ObjectStorage (GCS bucket) or DataWarehouse (BigQuery dataset)>'
permissions:
- datastream.streams.update
conditions:
- iam_permission
- api_enabled
state_logic: "ACTIVE if datastream.streams.update is confirmed in the effective grant and the principal\
\ can also create a destination connection profile pointing to an attacker-controlled sink (requires\
\ datastream.connectionProfiles.create or access to an existing attacker-controlled profile). If the\
\ principal can only update the stream (not create new profiles), the attack is CONDITIONAL on an\
\ existing attacker-controlled connection profile being available. BLOCKED if a deny policy applies\
\ to datastream.streams.update or if a VPC Service Controls perimeter blocks the API call. Note: stopping\
\ and restarting the stream (streams.start/stop) triggers a backfill \u2014 a FULL copy of the source\
\ database to the new destination."
confidence: 0.9
derived_from:
- <datastream.streams.update effective permission on principal>
false_positive_note: "The exfiltration requires: (1) the stream is RUNNING or can be started (datastream.streams.start);\
\ (2) the new destination connection profile is reachable from Datastream's service network; (3) the\
\ principal has write access to create or reference the attacker-controlled destination connection\
\ profile. Do NOT emit ACTIVE for a paused / stopped stream unless the principal also has datastream.streams.start.\
\ A VPC Service Controls perimeter restricting datastream.googleapis.com can block this path (emit\
\ BLOCKED with the perimeter as denied_by). Redirecting the stream does not provide the SOURCE database\
\ credentials \u2014 only the data flowing through the stream. The target_type is Storage because\
\ the actual exfiltration sink is an attacker-controlled GCS bucket or BigQuery dataset (both Storage\
\ class), not the stream's source database."
narrative: '{principal.name} can update the Datastream stream {stream.name} (datastream.streams.update)
to replace its destination configuration with an attacker-controlled BigQuery dataset or GCS bucket;
ongoing CDC data and any triggered backfill (full historical copy of {stream.name}''s source tables)
will be written to the attacker-controlled destination.'