gcp-tasks-execute-as-via-queue-sa-swap
Re-pointing the queue's OIDC/OAuth token SA (cloudtasks.queues.update + iam.serviceAccounts.actAs) causes every future dispatch to carry a token for the chosen SA - an attacker controlling the consumer endpoint captures the token and exercises the SA's privileges.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
Queue AND provider_type(?queue) == google.cloud.tasks.v2.Queue
?principal has confirmed iam.serviceAccounts.actAs on ?sa (enforced by CanPassIdentity)
?queue has httpTarget configured (HTTP target queue, not App Engine only)
?sa.privilege_level >= ?principal.privilege_level
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
CanModifyConfiguration ?queue
?principal ExposesCredential ?sa
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | <principal> |
| target | <chosen service account> |
| permissions | cloudtasks.queues.update iam.serviceAccounts.actAs |
| conditions | role_compatibility trigger_exists network_reachability |
| state logic | CONDITIONAL(trigger_exists) by default when the SA swap is confirmed but no confirmed endpoint control is established — the queue will eventually dispatch and expose the token once a task is created or dispatched, but the attacker does not yet capture it without controlling the consumer endpoint. ACTIVE when: (a) the principal also has gcp-tasks-uri-override-token-capture (matched optional[0]+[1]) confirming the attacker has set uriOverride to an attacker-controlled endpoint, OR (b) the principal separately controls the consumer endpoint (e.g., Cloud Run service with attacker-writable service account, or explicit network control). POTENTIAL(role_compatibility) when actAs on the SA is unknown/unresolved. Never stronger than the contributing CanModifyConfiguration / CanPassIdentity. |
Narrative
{principal.name} can re-point {queue.name} to use {sa.name} as its OIDC/OAuth token SA (cloudtasks.queues.update + iam.serviceAccounts.actAs). Every future dispatch carries a token for {sa.name}. If the attacker controls the consumer endpoint (e.g., via uriOverride redirect), the token is captured and {sa.name}'s privileges are exercised; otherwise, the attacker influences the legitimate consumer's execution via the SA's privileges.
Raw rule rules/derived/gcp/tasks.yaml
id: gcp-tasks-execute-as-via-queue-sa-swap
emits: CanExecuteAs
description: "Re-pointing the queue's OIDC/OAuth token SA (cloudtasks.queues.update + iam.serviceAccounts.actAs)\
\ causes every future dispatch to carry a token for the chosen SA \u2014 an attacker controlling the\
\ consumer endpoint captures the token and exercises the SA's privileges."
match:
- - ?principal
- CanModifyConfiguration
- ?queue
- - ?principal
- CanPassIdentity
- ?sa
where:
- node_type(?queue) == Queue AND provider_type(?queue) == google.cloud.tasks.v2.Queue
- ?principal has confirmed iam.serviceAccounts.actAs on ?sa (enforced by CanPassIdentity)
- ?queue has httpTarget configured (HTTP target queue, not App Engine only)
- ?sa.privilege_level >= ?principal.privilege_level
optional:
- - ?principal
- CanModifyConfiguration
- ?queue
- - ?principal
- ExposesCredential
- ?sa
emit:
source_type: Identity
target_type: Identity
source: <principal>
target: <chosen service account>
permissions:
- cloudtasks.queues.update
- iam.serviceAccounts.actAs
conditions:
- role_compatibility
- trigger_exists
- network_reachability
state_logic: "CONDITIONAL(trigger_exists) by default when the SA swap is confirmed but no confirmed\
\ endpoint control is established \u2014 the queue will eventually dispatch and expose the token once\
\ a task is created or dispatched, but the attacker does not yet capture it without controlling the\
\ consumer endpoint. ACTIVE when: (a) the principal also has gcp-tasks-uri-override-token-capture\
\ (matched optional[0]+[1]) confirming the attacker has set uriOverride to an attacker-controlled\
\ endpoint, OR (b) the principal separately controls the consumer endpoint (e.g., Cloud Run service\
\ with attacker-writable service account, or explicit network control). POTENTIAL(role_compatibility)\
\ when actAs on the SA is unknown/unresolved. Never stronger than the contributing CanModifyConfiguration\
\ / CanPassIdentity."
confidence: min(contributing_confidences) * 0.85
derived_from:
- ?principal CanModifyConfiguration ?queue (gcp-tasks-update-queue-config)
- ?principal CanPassIdentity ?sa (gcp-tasks-passidentity-queue-sa)
- ?principal ExposesCredential ?sa via uriOverride? [optional, elevates to ACTIVE]
false_positive_note: "The SA token is delivered to the consumer endpoint, not directly to the attacker.\
\ CONDITIONAL(trigger_exists) is the safe default: without confirmed endpoint control, the attacker\
\ influences the consumer's execution via the SA's token, but the legitimate consumer receives and\
\ processes the token. Full CanExecuteAs(SA) requires the attacker to also control the consumer endpoint\
\ (e.g., via gcp-tasks-uri-override-token-capture redirecting to an attacker endpoint) to capture\
\ the raw token. Dedupes with can-execute-as execute-as-via-config-identity-swap on (CanExecuteAs,\
\ ?principal, ?sa) \u2014 emit once. Only emit when ?sa.privilege_level >= ?principal.privilege_level.\
\ Requires actAs \u2014 never emit without confirmed actAs."
narrative: '{principal.name} can re-point {queue.name} to use {sa.name} as its OIDC/OAuth token SA (cloudtasks.queues.update
+ iam.serviceAccounts.actAs). Every future dispatch carries a token for {sa.name}. If the attacker
controls the consumer endpoint (e.g., via uriOverride redirect), the token is captured and {sa.name}''s
privileges are exercised; otherwise, the attacker influences the legitimate consumer''s execution
via the SA''s privileges.'