aws-sc-provision-product-executes-as

servicecatalog:ProvisionProduct or UpdateProvisionedProduct on a product bound to a LaunchRole constraint executes the product's CFN template as the launch role, even when the provisioning principal holds no CloudFormation or IAM permissions of their own. Any parameter-driven custom resource or UserData the user controls runs as the launch role.

derived aws emits CanExecuteAs

match

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

{'principal': None} CanTrigger {'product': None} {'product': None} ExecutesAs {'launch_role': None}

where

node_type(?product) == BuildWorker # AWS::ServiceCatalog::CloudFormationProduct ?product has a LaunchRole constraint with RoleArn = ?launch_role (from servicecatalog:DescribeConstraint.ConstraintParameters.RoleArn) ?principal has EFFECTIVE servicecatalog:ProvisionProduct OR servicecatalog:UpdateProvisionedProduct on ?product (via portfolio association)

emit

source typeIdentity
target typeIdentity
source?principal
target?launch_role
permissionsservicecatalog:ProvisionProduct servicecatalog:UpdateProvisionedProduct
conditionsiam_permission service_state
state logicCONDITIONAL(parameter_controlled_resource) by default: the principal holds only ProvisionProduct / UpdateProvisionedProduct without template control. Escalation requires the product template to contain a parameter-driven resource (custom resource Lambda ServiceToken, UserData, or other attacker-influenceable path) that the provisioning user can reach. A product template with no attacker-influenceable resource path and rigid parameter validation is not trivially exploitable via parameters alone. BLOCKED when an SCP or permission boundary denies servicecatalog:ProvisionProduct or servicecatalog:UpdateProvisionedProduct at the effective scope; or when no LaunchRole constraint is present (then only the caller's own identity is used — no escalation).

Narrative

{principal.name} can provision product {product.name} (servicecatalog:ProvisionProduct / UpdateProvisionedProduct). {product.name} has a Launch Role Constraint binding it to {launch_role.name}; Service Catalog assumes {launch_role.name} to execute the product's CloudFormation template. Any attacker-influenced parameter path (custom resource, UserData, Fn::If branch) in the template runs as {launch_role.name}, granting {launch_role.name}'s permissions to {principal.name}.

Raw rule rules/derived/aws/servicecatalog.yaml

id: aws-sc-provision-product-executes-as
emits: CanExecuteAs
description: servicecatalog:ProvisionProduct or UpdateProvisionedProduct on a product bound to a LaunchRole
  constraint executes the product's CFN template as the launch role, even when the provisioning principal
  holds no CloudFormation or IAM permissions of their own. Any parameter-driven custom resource or UserData
  the user controls runs as the launch role.
match:
- - principal: null
  - CanTrigger
  - product: null
- - product: null
  - ExecutesAs
  - launch_role: null
where:
- 'node_type(?product) == BuildWorker  # AWS::ServiceCatalog::CloudFormationProduct'
- ?product has a LaunchRole constraint with RoleArn = ?launch_role (from servicecatalog:DescribeConstraint.ConstraintParameters.RoleArn)
- ?principal has EFFECTIVE servicecatalog:ProvisionProduct OR servicecatalog:UpdateProvisionedProduct
  on ?product (via portfolio association)
emit:
  source_type: Identity
  target_type: Identity
  source: ?principal
  target: ?launch_role
  permissions:
  - servicecatalog:ProvisionProduct
  - servicecatalog:UpdateProvisionedProduct
  conditions:
  - iam_permission
  - service_state
  state_logic: "CONDITIONAL(parameter_controlled_resource) by default: the principal holds only ProvisionProduct\
    \ / UpdateProvisionedProduct without template control. Escalation requires the product template to\
    \ contain a parameter-driven resource (custom resource Lambda ServiceToken, UserData, or other attacker-influenceable\
    \ path) that the provisioning user can reach. A product template with no attacker-influenceable resource\
    \ path and rigid parameter validation is not trivially exploitable via parameters alone. BLOCKED when\
    \ an SCP or permission boundary denies servicecatalog:ProvisionProduct or servicecatalog:UpdateProvisionedProduct\
    \ at the effective scope; or when no LaunchRole constraint is present (then only the caller's own\
    \ identity is used \u2014 no escalation)."
  confidence: 0.8
  derived_from:
  - ?principal CanTrigger ?product
  - ?product ExecutesAs ?launch_role (LaunchRole constraint)
  - cicd-chains.yaml cicd-trigger-runner-executes-as (trigger + ExecutesAs pattern)
  false_positive_note: "The most critical false-positive risk: products WITHOUT a LaunchRole constraint\
    \ execute as the CALLER's own identity \u2014 no escalation, do not emit this edge. Always confirm\
    \ a LaunchRole constraint is present (ConstraintType == LAUNCH, ConstraintParameters.RoleArn non-empty).\
    \ A LocalRoleName constraint uses a role in the END-USER's account; verify that role's privilege before\
    \ treating this as a high-confidence escalation. The CONDITIONAL state reflects that bare ProvisionProduct\
    \ access is only CanExecuteAs when the template has an attacker-controllable execution path \u2014\
    \ a template with fully controlled parameters (static values, validation patterns that block injection)\
    \ and no custom resources is not trivially exploitable; keep CONDITIONAL, not ACTIVE, until a parameter-driven\
    \ execution path is confirmed. ServiceActionAssociation-based execution (ExecuteProvisionedProductServiceAction)\
    \ is a separate path against the SSM Automation role \u2014 not modeled here."
  narrative: '{principal.name} can provision product {product.name} (servicecatalog:ProvisionProduct /
    UpdateProvisionedProduct). {product.name} has a Launch Role Constraint binding it to {launch_role.name};
    Service Catalog assumes {launch_role.name} to execute the product''s CloudFormation template. Any
    attacker-influenced parameter path (custom resource, UserData, Fn::If branch) in the template runs
    as {launch_role.name}, granting {launch_role.name}''s permissions to {principal.name}.'
move · open · esc close