aws-cfn-update-stack-modifies-code
cloudformation:UpdateStack lets a caller replace the template a stack executes, making the stack run attacker-chosen resource API calls as its 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:UpdateStack on ?stack
?stack has RoleARN set (service role exists — DescribeStacks RoleARN is non-empty)
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?stack |
| permissions | cloudformation:UpdateStack |
| conditions | iam_permission service_state |
| state logic | ACTIVE when ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack AND the stack is in a mutable state (CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE — not in *_IN_PROGRESS or DELETE_*); otherwise CONDITIONAL(service_state). BLOCKED if an SCP or permission boundary denies cloudformation:UpdateStack or iam:PassRole on the service role at the effective scope. |
Narrative
{principal.name} can call cloudformation:UpdateStack on stack {stack.name}, replacing the template it executes. Because {stack.name} runs as {stack.RoleARN} (its service role), the next stack update executes the attacker's template resources as that role. cicd-chains propagates this to CanExecuteAs.
Raw rule rules/derived/aws/cloudformation.yaml
id: aws-cfn-update-stack-modifies-code
emits: CanModifyCode
description: cloudformation:UpdateStack lets a caller replace the template a stack executes, making the
stack run attacker-chosen resource API calls as its service role.
match:
- - principal: null
- HasPermission
- stack: null
where:
- 'node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack'
- ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack
- "?stack has RoleARN set (service role exists \u2014 DescribeStacks RoleARN is non-empty)"
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?stack
permissions:
- cloudformation:UpdateStack
conditions:
- iam_permission
- service_state
state_logic: "ACTIVE when ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack AND the stack\
\ is in a mutable state (CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE \u2014 not in\
\ *_IN_PROGRESS or DELETE_*); otherwise CONDITIONAL(service_state). BLOCKED if an SCP or permission\
\ boundary denies cloudformation:UpdateStack or iam:PassRole on the service role at the effective\
\ scope."
confidence: 0.95
derived_from:
- cloudformation:UpdateStack EFFECTIVE permission on ?stack
- ?stack RoleARN is set
false_positive_note: "Only emit when the stack has a RoleARN (service role). Without --role-arn, CloudFormation\
\ uses the CALLER's own identity for resource API calls \u2014 no privilege over the caller, no CanModifyCode\
\ escalation surface. The cloudformation:UpdateStack action must be in the principal's EFFECTIVE policy\
\ (post identity+resource+SCP+boundary evaluation) scoped to this stack's ARN. If cloudformation:UpdateStack\
\ is denied by an SCP or permission boundary, emit BLOCKED. cicd-chains cicd-modify-buildspec-executes-as\
\ rolls this CanModifyCode + the stack's ExecutesAs into CanExecuteAs; do NOT re-derive CanExecuteAs\
\ here."
narrative: '{principal.name} can call cloudformation:UpdateStack on stack {stack.name}, replacing the
template it executes. Because {stack.name} runs as {stack.RoleARN} (its service role), the next stack
update executes the attacker''s template resources as that role. cicd-chains propagates this to CanExecuteAs.'