aws-cfn-update-stack-role-swap-modifies-config

cloudformation:UpdateStack changing only the stack's service role (--role-arn) to a more-privileged role: swaps the execution identity without modifying the template.

derived aws emits CanModifyConfiguration

match

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

{'principal': None} CanPassIdentity {'new_role': None}

where

?principal has EFFECTIVE cloudformation:UpdateStack on ?stack node_type(?stack) == BuildWorker # AWS::CloudFormation::Stack ?stack has RoleARN set (current service role exists) ?new_role is more privileged than the stack's current RoleARN OR ?new_role is a different privileged role iam:PassRole for ?new_role permits iam:PassedToService == cloudformation.amazonaws.com (or unconditioned)

emit

source typeIdentity
target typeCompute
source?principal
target?stack
permissionscloudformation:UpdateStack iam:PassRole
conditionsiam_permission role_compatibility service_state
state logicACTIVE when (a) ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack, (b) iam:PassRole for ?new_role is ACTIVE with iam:PassedToService including cloudformation.amazonaws.com or unconditioned, AND (c) the stack is mutable (CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE). CONDITIONAL(role_compatibility) when ?new_role trust policy does not yet include cloudformation.amazonaws.com — UpdateStack will fail because CloudFormation cannot assume the role. BLOCKED if SCP or permission boundary denies cloudformation:UpdateStack or iam:PassRole at the effective scope.

Narrative

{principal.name} can call cloudformation:UpdateStack on stack {stack.name} to change its service role (--role-arn) and has iam:PassRole for {new_role.name} (iam:PassedToService == cloudformation.amazonaws.com). By updating the stack with only a new RoleARN and no template change, the attacker rebinds the stack to execute as {new_role.name} without CloudFormation modification logic interfering. On the next stack operation, resource API calls run as {new_role.name}. can-execute-as propagates this to CanExecuteAs({principal.name} -> {new_role.name}).

Raw rule rules/derived/aws/cloudformation.yaml

id: aws-cfn-update-stack-role-swap-modifies-config
emits: CanModifyConfiguration
description: 'cloudformation:UpdateStack changing only the stack''s service role (--role-arn) to a more-privileged
  role: swaps the execution identity without modifying the template.'
match:
- - principal: null
  - CanPassIdentity
  - new_role: null
where:
- ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack
- 'node_type(?stack) == BuildWorker  # AWS::CloudFormation::Stack'
- ?stack has RoleARN set (current service role exists)
- ?new_role is more privileged than the stack's current RoleARN OR ?new_role is a different privileged
  role
- iam:PassRole for ?new_role permits iam:PassedToService == cloudformation.amazonaws.com (or unconditioned)
emit:
  source_type: Identity
  target_type: Compute
  source: ?principal
  target: ?stack
  permissions:
  - cloudformation:UpdateStack
  - iam:PassRole
  conditions:
  - iam_permission
  - role_compatibility
  - service_state
  state_logic: "ACTIVE when (a) ?principal has EFFECTIVE cloudformation:UpdateStack on ?stack, (b) iam:PassRole\
    \ for ?new_role is ACTIVE with iam:PassedToService including cloudformation.amazonaws.com or unconditioned,\
    \ AND (c) the stack is mutable (CREATE_COMPLETE, UPDATE_COMPLETE, UPDATE_ROLLBACK_COMPLETE). CONDITIONAL(role_compatibility)\
    \ when ?new_role trust policy does not yet include cloudformation.amazonaws.com \u2014 UpdateStack\
    \ will fail because CloudFormation cannot assume the role. BLOCKED if SCP or permission boundary denies\
    \ cloudformation:UpdateStack or iam:PassRole at the effective scope."
  confidence: 0.92
  derived_from:
  - ?principal CanPassIdentity ?new_role
  - cloudformation:UpdateStack EFFECTIVE on ?stack
  - ?stack RoleARN is set
  false_positive_note: "CanModifyConfiguration alone (without CanPassIdentity to a more-privileged role)\
    \ is not escalation \u2014 swapping to a less-privileged or sideways role is not an attack. Only emit\
    \ when ?new_role is provably more privileged or when the principal can demonstrate arbitrary role\
    \ choice. Honor iam:PassedToService: a role passable only to services other than cloudformation.amazonaws.com\
    \ does NOT qualify. can-execute-as.yaml execute-as-via-config-identity-swap rolls CanModifyConfiguration\
    \ + CanPassIdentity into CanExecuteAs \u2014 cite, do not re-derive CanExecuteAs here."
  narrative: '{principal.name} can call cloudformation:UpdateStack on stack {stack.name} to change its
    service role (--role-arn) and has iam:PassRole for {new_role.name} (iam:PassedToService == cloudformation.amazonaws.com).
    By updating the stack with only a new RoleARN and no template change, the attacker rebinds the stack
    to execute as {new_role.name} without CloudFormation modification logic interfering. On the next stack
    operation, resource API calls run as {new_role.name}. can-execute-as propagates this to CanExecuteAs({principal.name}
    -> {new_role.name}).'
move · open · esc close