aws-datapipeline-execute-as-passrole-rce

Create a pipeline with a ShellCommandActivity injecting attacker commands, pass a privileged role as resourceRole, and activate: the EC2 resource runs attacker code as that role. The classic Data Pipeline PassRole RCE.

derived aws emits CanExecuteAs

match

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

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

where

node_type(?pipeline) == Workflow ?pipeline.provider_type == 'AWS::DataPipeline::Pipeline' ?principal has EFFECTIVE datapipeline:CreatePipeline OR datapipeline:PutPipelineDefinition ?role trust policy allows ec2.amazonaws.com (resourceRole passable as instance profile) iam:PassedToService condition (if any) includes ec2.amazonaws.com or is unconditioned

optional

Present → upgrade the emitted state; absent → downgrade (not a hard requirement).

{'principal': None} CanTrigger {'pipeline': None}

emit

source typeIdentity
target typeIdentity
source?principal
target?role
permissionsdatapipeline:CreatePipeline datapipeline:PutPipelineDefinition datapipeline:ActivatePipeline iam:PassRole
conditionsiam_permission condition_expression role_compatibility trigger_exists
state logicACTIVE when: (a) principal has EFFECTIVE datapipeline:PutPipelineDefinition (or datapipeline:CreatePipeline for a new pipeline) — confirmed CanModifyCode; (b) principal has EFFECTIVE iam:PassRole on ?role admitting ec2.amazonaws.com — confirmed CanPassIdentity; AND (c) a run path exists: either the principal has CanTrigger(principal, pipeline) via datapipeline:ActivatePipeline or datapipeline:SetStatus (matched optional[0]) OR the pipeline already has an active cron/timeseries schedule that will run the next interval. CONDITIONAL(trigger_exists) when neither CanTrigger is confirmed nor an active schedule exists (code is planted but has no established run path). CONDITIONAL (role_compatibility) when the role trust or PassedToService condition is unknown/unresolved (downgrade to POTENTIAL when trust demonstrably excludes ec2.amazonaws.com). BLOCKED by SCP/boundary deny on datapipeline:PutPipelineDefinition, CanTrigger, or iam:PassRole at the effective scope.

Narrative

{principal.name} can define a Data Pipeline with a ShellCommandActivity that runs attacker-chosen commands (datapipeline:PutPipelineDefinition / datapipeline:CreatePipeline), pass {role.name} as the resourceRole (iam:PassRole → ec2.amazonaws.com), and activate the pipeline (datapipeline:ActivatePipeline). The EC2 resource node runs the injected command and {role.name}'s credentials are accessible via IMDS - executing arbitrary code as {role.name}. This is the canonical AWS Data Pipeline PassRole RCE (see can-execute-as:execute-as-via-code-modify and cicd-chains:cicd-modify-buildspec-executes-as for the roll-up derivation).

Raw rule rules/derived/aws/datapipeline.yaml

id: aws-datapipeline-execute-as-passrole-rce
emits: CanExecuteAs
description: 'Create a pipeline with a ShellCommandActivity injecting attacker commands, pass a privileged
  role as resourceRole, and activate: the EC2 resource runs attacker code as that role. The classic Data
  Pipeline PassRole RCE.'
match:
- - principal: null
  - CanModifyCode
  - pipeline: null
- - principal: null
  - CanPassIdentity
  - role: null
where:
- node_type(?pipeline) == Workflow
- ?pipeline.provider_type == 'AWS::DataPipeline::Pipeline'
- ?principal has EFFECTIVE datapipeline:CreatePipeline OR datapipeline:PutPipelineDefinition
- ?role trust policy allows ec2.amazonaws.com (resourceRole passable as instance profile)
- iam:PassedToService condition (if any) includes ec2.amazonaws.com or is unconditioned
optional:
- - principal: null
  - CanTrigger
  - pipeline: null
emit:
  source_type: Identity
  target_type: Identity
  source: ?principal
  target: ?role
  permissions:
  - datapipeline:CreatePipeline
  - datapipeline:PutPipelineDefinition
  - datapipeline:ActivatePipeline
  - iam:PassRole
  conditions:
  - iam_permission
  - condition_expression
  - role_compatibility
  - trigger_exists
  state_logic: "ACTIVE when: (a) principal has EFFECTIVE datapipeline:PutPipelineDefinition (or datapipeline:CreatePipeline\
    \ for a new pipeline) \u2014 confirmed CanModifyCode; (b) principal has EFFECTIVE iam:PassRole on\
    \ ?role admitting ec2.amazonaws.com \u2014 confirmed CanPassIdentity; AND (c) a run path exists: either\
    \ the principal has CanTrigger(principal, pipeline) via datapipeline:ActivatePipeline or datapipeline:SetStatus\
    \ (matched optional[0]) OR the pipeline already has an active cron/timeseries schedule that will run\
    \ the next interval. CONDITIONAL(trigger_exists) when neither CanTrigger is confirmed nor an active\
    \ schedule exists (code is planted but has no established run path). CONDITIONAL (role_compatibility)\
    \ when the role trust or PassedToService condition is unknown/unresolved (downgrade to POTENTIAL when\
    \ trust demonstrably excludes ec2.amazonaws.com). BLOCKED by SCP/boundary deny on datapipeline:PutPipelineDefinition,\
    \ CanTrigger, or iam:PassRole at the effective scope."
  confidence: min(contributing_confidences) * 0.95
  derived_from:
  - ?principal CanModifyCode ?pipeline (aws-datapipeline-put-definition-code)
  - ?principal CanPassIdentity ?role (aws-datapipeline-pass-resource-role)
  - "can-execute-as:execute-as-via-code-modify (linchpin \u2014 cited not re-derived)"
  - "cicd-chains:cicd-modify-buildspec-executes-as (linchpin \u2014 cited; pipeline def == buildspec)"
  false_positive_note: "ALL of the following must hold for ACTIVE: (1) datapipeline:PutPipelineDefinition\
    \ is EFFECTIVE on the target pipeline \u2014 honor tag-based conditions (datapipeline:Tag), pipeline-creator\
    \ conditions (datapipeline:PipelineCreator), and workerGroup conditions. (2) iam:PassRole is EFFECTIVE\
    \ and scoped to admit the target role for ec2.amazonaws.com: a role passable only to lambda.amazonaws.com,\
    \ ecs-tasks.amazonaws.com, or another service is NOT passable as a Data Pipeline resourceRole \u2014\
    \ downgrade to POTENTIAL(role_compatibility). (3) A run path (ActivatePipeline or active schedule)\
    \ is established. Without (3), the definition is modified but never executes: CONDITIONAL(trigger_exists).\
    \ Do not emit ACTIVE if only datapipeline:ValidatePipelineDefinition is present (validation does not\
    \ write or activate). Cite can-execute-as and cicd-chains for the CanExecuteAs roll-up logic; do not\
    \ re-derive it."
  narrative: "{principal.name} can define a Data Pipeline with a ShellCommandActivity that runs attacker-chosen\
    \ commands (datapipeline:PutPipelineDefinition / datapipeline:CreatePipeline), pass {role.name} as\
    \ the resourceRole (iam:PassRole \u2192 ec2.amazonaws.com), and activate the pipeline (datapipeline:ActivatePipeline).\
    \ The EC2 resource node runs the injected command and {role.name}'s credentials are accessible via\
    \ IMDS \u2014 executing arbitrary code as {role.name}. This is the canonical AWS Data Pipeline PassRole\
    \ RCE (see can-execute-as:execute-as-via-code-modify and cicd-chains:cicd-modify-buildspec-executes-as\
    \ for the roll-up derivation)."
move · open · esc close