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.

derived aws emits CanModifyCode

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} HasPermission {'stack': None}

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 typeIdentity
target typeCompute
source?principal
target?stack
permissionscloudformation:UpdateStack
conditionsiam_permission service_state
state logicACTIVE 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.'
move · open · esc close