aws-cfn-changeset-execute-modifies-code
cloudformation:CreateChangeSet with attacker template + cloudformation:ExecuteChangeSet applies the attacker's template to the stack, running as the service role.
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:CreateChangeSet on ?stack
?principal has EFFECTIVE cloudformation:ExecuteChangeSet on ?stack
?stack has RoleARN set (service role exists)
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?stack |
| permissions | cloudformation:CreateChangeSet cloudformation:ExecuteChangeSet |
| conditions | iam_permission service_state |
| state logic | ACTIVE when both cloudformation:CreateChangeSet AND cloudformation:ExecuteChangeSet are in the principal's EFFECTIVE policy on ?stack AND the stack is mutable (CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE). If only CreateChangeSet is held (not ExecuteChangeSet), downgrade to CONDITIONAL(approval_required) — the change set exists but a separate approver must execute it. BLOCKED if SCP/boundary denies either cloudformation:CreateChangeSet or cloudformation:ExecuteChangeSet at the effective scope. |
Narrative
{principal.name} can create a change set on stack {stack.name} with an attacker- supplied template (cloudformation:CreateChangeSet) and then execute it (cloudformation:ExecuteChangeSet), applying arbitrary resource API calls as the stack's service role.
Raw rule rules/derived/aws/cloudformation.yaml
id: aws-cfn-changeset-execute-modifies-code
emits: CanModifyCode
description: cloudformation:CreateChangeSet with attacker template + cloudformation:ExecuteChangeSet applies
the attacker's template to the stack, running as the service role.
match:
- - principal: null
- HasPermission
- stack: null
where:
- 'node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack'
- ?principal has EFFECTIVE cloudformation:CreateChangeSet on ?stack
- ?principal has EFFECTIVE cloudformation:ExecuteChangeSet on ?stack
- ?stack has RoleARN set (service role exists)
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?stack
permissions:
- cloudformation:CreateChangeSet
- cloudformation:ExecuteChangeSet
conditions:
- iam_permission
- service_state
state_logic: "ACTIVE when both cloudformation:CreateChangeSet AND cloudformation:ExecuteChangeSet are\
\ in the principal's EFFECTIVE policy on ?stack AND the stack is mutable (CREATE_COMPLETE, UPDATE_COMPLETE,\
\ UPDATE_ROLLBACK_COMPLETE). If only CreateChangeSet is held (not ExecuteChangeSet), downgrade to\
\ CONDITIONAL(approval_required) \u2014 the change set exists but a separate approver must execute\
\ it. BLOCKED if SCP/boundary denies either cloudformation:CreateChangeSet or cloudformation:ExecuteChangeSet\
\ at the effective scope."
confidence: 0.93
derived_from:
- cloudformation:CreateChangeSet EFFECTIVE on ?stack
- cloudformation:ExecuteChangeSet EFFECTIVE on ?stack
- ?stack RoleARN is set
false_positive_note: "Holding ONLY ExecuteChangeSet (approver role) without CreateChangeSet is NOT attacker-controlled\
\ code injection \u2014 the change set was authored by someone else. Holding ONLY CreateChangeSet\
\ is CONDITIONAL(approval_required) \u2014 the change set must be approved/executed by another principal.\
\ Do NOT emit ACTIVE unless both actions are held on the same stack. cicd-chains collapses this CanModifyCode\
\ into CanExecuteAs; do not re-derive CanExecuteAs here."
narrative: '{principal.name} can create a change set on stack {stack.name} with an attacker- supplied
template (cloudformation:CreateChangeSet) and then execute it (cloudformation:ExecuteChangeSet), applying
arbitrary resource API calls as the stack''s service role.'