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.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
CanModifyCode {'pipeline': None}
{'principal': None} CanPassIdentity {'role': None}
where
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).
CanTrigger {'pipeline': None}
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) — 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)."