aws-imagebuilder-container-recipe-poisons-ecr

Modify an Image Builder container recipe (or the pipeline referencing it) and trigger a build to push a poisoned container image to the target ECR repository, feeding the container-chains supply-chain fan-out to all downstream consumers.

derived aws emits CanModifyCode

match

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

{'principal': None} CanModifyCode {'pipeline': None}

where

node_type(?pipeline) == BuildWorker ?pipeline.provider_type == 'AWS::ImageBuilder::ImagePipeline' ?pipeline.containerRecipeArn != null # pipeline uses a container recipe, not an AMI recipe ?ecr_repository is the targetRepository of the container recipe used by ?pipeline node_type(?ecr_repository) == ContainerRegistry

emit

source typeIdentity
target typeContainerRegistry
source?principal
target?ecr_repository
permissionsimagebuilder:CreateComponent imagebuilder:CreateContainerRecipe imagebuilder:UpdateImagePipeline imagebuilder:StartImagePipelineExecution?
conditionsrun_path
state logicInherit state from the contributing CanModifyCode(principal -> pipeline): ACTIVE if that edge is ACTIVE (run path exists + definition is modifiable); CONDITIONAL(run_path) if the modification is planted but no run path fires the build; BLOCKED if the pipeline CanModifyCode is BLOCKED. container-chains image-push-poisons-consumers (cited, not re-derived) then propagates this CanModifyCode to each downstream consumer of the ECR tag, and poisoned-image-executes-as (cited) produces CanExecuteAs the consumer's identity. This rule only emits the ECR CanModifyCode fact that container-chains needs.

Narrative

{principal.name} can modify the container recipe of {pipeline.name} and trigger a build, causing Image Builder to push a poisoned OCI image to {ecr_repository.name}. Every workload (ECS task, EKS pod, Lambda container) that re-pulls the affected tag executes attacker-controlled layers (container-chains image-push-poisons-consumers fans out; poisoned-image-executes-as produces the CanExecuteAs each workload's identity).

Raw rule rules/derived/aws/imagebuilder.yaml

id: aws-imagebuilder-container-recipe-poisons-ecr
emits: CanModifyCode
description: Modify an Image Builder container recipe (or the pipeline referencing it) and trigger a build
  to push a poisoned container image to the target ECR repository, feeding the container-chains supply-chain
  fan-out to all downstream consumers.
match:
- - principal: null
  - CanModifyCode
  - pipeline: null
where:
- node_type(?pipeline) == BuildWorker
- ?pipeline.provider_type == 'AWS::ImageBuilder::ImagePipeline'
- '?pipeline.containerRecipeArn != null  # pipeline uses a container recipe, not an AMI recipe'
- ?ecr_repository is the targetRepository of the container recipe used by ?pipeline
- node_type(?ecr_repository) == ContainerRegistry
emit:
  source_type: Identity
  target_type: ContainerRegistry
  source: ?principal
  target: ?ecr_repository
  permissions:
  - imagebuilder:CreateComponent
  - imagebuilder:CreateContainerRecipe
  - imagebuilder:UpdateImagePipeline
  - imagebuilder:StartImagePipelineExecution?
  conditions:
  - run_path
  state_logic: 'Inherit state from the contributing CanModifyCode(principal -> pipeline): ACTIVE if that
    edge is ACTIVE (run path exists + definition is modifiable); CONDITIONAL(run_path) if the modification
    is planted but no run path fires the build; BLOCKED if the pipeline CanModifyCode is BLOCKED. container-chains
    image-push-poisons-consumers (cited, not re-derived) then propagates this CanModifyCode to each downstream
    consumer of the ECR tag, and poisoned-image-executes-as (cited) produces CanExecuteAs the consumer''s
    identity. This rule only emits the ECR CanModifyCode fact that container-chains needs.'
  confidence: min(contributing_confidences) * 0.92
  derived_from:
  - ?principal CanModifyCode ?pipeline (imagebuilder rules 1-2, including container-recipe path)
  - ?pipeline ContainerRecipe.targetRepository -> ?ecr_repository
  - "container-chains image-push-poisons-consumers (cited \u2014 propagates to consumers)"
  - "container-chains poisoned-image-executes-as (cited \u2014 produces CanExecuteAs consumer identity)"
  false_positive_note: "Only emit when the pipeline uses a CONTAINER recipe (containerRecipeArn set),\
    \ not an AMI recipe. The build pushes to the targetRepository tag specified in the container recipe;\
    \ if the tag is immutable in the ECR repository, the push will fail \u2014 downgrade the CanModifyCode\
    \ to CONDITIONAL(image_tag_mutability). Verify the targetRepository ARN resolves to a real ContainerRegistry\
    \ node. container-chains handles the fan-out; do not re-derive which downstream workloads are affected\
    \ here. The CanModifyCode edge should come from either aws-imagebuilder-modify-component-executes-as\
    \ (rule 1, for component paths), aws-imagebuilder-update-pipeline-executes-as (rule 2, for recipe\
    \ swap paths), or aws-imagebuilder-can-modify-code-container (explicit rule for CreateContainerRecipe\
    \ path)."
  narrative: '{principal.name} can modify the container recipe of {pipeline.name} and trigger a build,
    causing Image Builder to push a poisoned OCI image to {ecr_repository.name}. Every workload (ECS task,
    EKS pod, Lambda container) that re-pulls the affected tag executes attacker-controlled layers (container-chains
    image-push-poisons-consumers fans out; poisoned-image-executes-as produces the CanExecuteAs each workload''s
    identity).'
move · open · esc close