aws-cfn-execute-changeset-approve-path
cloudformation:ExecuteChangeSet alone (approver role, no CreateChangeSet): can apply an attacker-authored pending change set if one already exists - CONDITIONAL on attacker-authored change set being present.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?stack) ==
BuildWorker # AWS::CloudFormation::Stack
?principal has EFFECTIVE cloudformation:ExecuteChangeSet on ?stack
?principal does NOT have cloudformation:CreateChangeSet on ?stack (pure approver)
?stack has RoleARN set
exists a pending change set on ?stack created by a different (potentially attacker-controlled) principal
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?stack |
| permissions | cloudformation:ExecuteChangeSet |
| conditions | attacker_authored_changeset |
| state logic | CONDITIONAL(attacker_authored_changeset) — this edge is only meaningful if an attacker-authored change set is already pending on the stack. Without the attacker- authored change set, ExecuteChangeSet alone applies a trusted change set and is not code injection. ACTIVE only when (a) a change set authored by an attacker-controlled principal exists in REVIEW_IN_PROGRESS status AND (b) the approver principal can execute any pending change set without reviewing the template diff. Inherits weakest with the contributing change set evidence. |
Narrative
{principal.name} can execute pending change sets on stack {stack.name} (cloudformation:ExecuteChangeSet); if an attacker-authored change set is already pending, approving it applies the attacker's template as the stack's service role.
Raw rule rules/derived/aws/cloudformation.yaml
id: aws-cfn-execute-changeset-approve-path
emits: CanModifyCode
description: "cloudformation:ExecuteChangeSet alone (approver role, no CreateChangeSet): can apply an\
\ attacker-authored pending change set if one already exists \u2014 CONDITIONAL on attacker-authored\
\ change set being present."
match:
- - principal: null
- HasPermission
- stack: null
where:
- 'node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack'
- ?principal has EFFECTIVE cloudformation:ExecuteChangeSet on ?stack
- ?principal does NOT have cloudformation:CreateChangeSet on ?stack (pure approver)
- ?stack has RoleARN set
- exists a pending change set on ?stack created by a different (potentially attacker-controlled) principal
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?stack
permissions:
- cloudformation:ExecuteChangeSet
conditions:
- attacker_authored_changeset
state_logic: "CONDITIONAL(attacker_authored_changeset) \u2014 this edge is only meaningful if an attacker-authored\
\ change set is already pending on the stack. Without the attacker- authored change set, ExecuteChangeSet\
\ alone applies a trusted change set and is not code injection. ACTIVE only when (a) a change set\
\ authored by an attacker-controlled principal exists in REVIEW_IN_PROGRESS status AND (b) the approver\
\ principal can execute any pending change set without reviewing the template diff. Inherits weakest\
\ with the contributing change set evidence."
confidence: 0.7
derived_from:
- cloudformation:ExecuteChangeSet EFFECTIVE on ?stack
- ?stack RoleARN is set
- pending attacker-authored change set on ?stack
false_positive_note: Strictly weaker (0.7 prior) than the combined CreateChangeSet+ExecuteChangeSet
path. Do NOT emit ACTIVE for a clean, trusted change set approved in normal workflow. This path only
matters in threat models where a separate attacker-controlled principal can CREATE change sets (social
engineering, compromised pipeline, SaaS integration). cicd-chains / can-execute-as propagate to CanExecuteAs
only when ACTIVE.
narrative: '{principal.name} can execute pending change sets on stack {stack.name} (cloudformation:ExecuteChangeSet);
if an attacker-authored change set is already pending, approving it applies the attacker''s template
as the stack''s service role.'