gcp-eventarc-execute-as-via-redirect
Update an existing Eventarc trigger's destination to point at attacker-controlled or higher-privilege compute, then wait for a natural event to fire, gaining execution as the new destination's runtime SA.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanModifyConfiguration {'trigger': None}
{'trigger': None} CanTrigger {'consumer': None}
{'consumer': None} ExecutesAs {'sa': None}
where
node_type(?trigger) ==
EventRule
node_class(?consumer) == Compute or node_type(?consumer) == Workflow
?sa.privilege_level >= ?principal.privilege_level
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | <principal> |
| target | <consumer's runtime service account> |
| permissions | eventarc.triggers.update iam.serviceAccounts.actAs? |
| conditions | trigger_exists role_compatibility |
| state logic | CONDITIONAL(trigger_exists) — the trigger update is immediate (ACTIVE once update succeeds) but the consumer only executes when the next matching event arrives from the (unchanged) event source. The ACTIVE gate requires both: (a) CanModifyConfiguration(trigger) is ACTIVE and (b) the trigger SA holds invoker rights on the new destination. POTENTIAL(role_compatibility) when the trigger SA's invoker rights on the new destination are unconfirmed. If the trigger's event source fires frequently (e.g. high-volume Audit Log activity), CONDITIONAL is nearly equivalent to ACTIVE in practice. |
Narrative
{principal.name} can update trigger {trigger.name} (eventarc.triggers.update) to redirect its event delivery to {consumer.name}; the next matching natural event causes {consumer.name} to run as {sa.name} with an attacker-influenced payload.
Raw rule rules/derived/gcp/eventarc.yaml
id: gcp-eventarc-execute-as-via-redirect
emits: CanExecuteAs
description: Update an existing Eventarc trigger's destination to point at attacker-controlled or higher-privilege
compute, then wait for a natural event to fire, gaining execution as the new destination's runtime SA.
match:
- - principal: null
- CanModifyConfiguration
- trigger: null
- - trigger: null
- CanTrigger
- consumer: null
- - consumer: null
- ExecutesAs
- sa: null
where:
- node_type(?trigger) == EventRule
- node_class(?consumer) == Compute or node_type(?consumer) == Workflow
- ?sa.privilege_level >= ?principal.privilege_level
emit:
source_type: Identity
target_type: Identity
source: <principal>
target: <consumer's runtime service account>
permissions:
- eventarc.triggers.update
- iam.serviceAccounts.actAs?
conditions:
- trigger_exists
- role_compatibility
state_logic: "CONDITIONAL(trigger_exists) \u2014 the trigger update is immediate (ACTIVE once update\
\ succeeds) but the consumer only executes when the next matching event arrives from the (unchanged)\
\ event source. The ACTIVE gate requires both: (a) CanModifyConfiguration(trigger) is ACTIVE and (b)\
\ the trigger SA holds invoker rights on the new destination. POTENTIAL(role_compatibility) when the\
\ trigger SA's invoker rights on the new destination are unconfirmed. If the trigger's event source\
\ fires frequently (e.g. high-volume Audit Log activity), CONDITIONAL is nearly equivalent to ACTIVE\
\ in practice."
confidence: min(contributing_confidences) * 0.85
derived_from:
- ?principal CanModifyConfiguration ?trigger (gcp-eventarc-update-trigger)
- ?trigger CanTrigger ?consumer (gcp-eventarc-trigger-routes-to-consumer)
- ?consumer ExecutesAs ?sa (from consumer's rule file)
false_positive_note: "Destination redirect is the strongest Eventarc escalation because it hijacks an\
\ existing live event stream (no attacker-controlled publish needed). Key gates: (1) the trigger SA\
\ must have invoker rights on the NEW destination (roles/run.invoker / roles/workflows.invoker); if\
\ not, delivery fails \u2014 downgrade to POTENTIAL(role_compatibility). (2) If the trigger SA must\
\ also be changed (the existing SA lacks invoker on the new destination), actAs on the new trigger\
\ SA is required. (3) The redirected consumer must be attacker-controlled or must run as a SA the\
\ attacker wants to execute as (modeling requires knowing the new destination's ExecutesAs edge).\
\ De-dupes with messaging-chains messaging-trigger-executes-as on (CanExecuteAs, ?principal, ?sa)."
narrative: '{principal.name} can update trigger {trigger.name} (eventarc.triggers.update) to redirect
its event delivery to {consumer.name}; the next matching natural event causes {consumer.name} to run
as {sa.name} with an attacker-influenced payload.'