aws-datapipeline-execute-as-existing-def
Inject shell commands into an existing pipeline definition and activate it to execute as the resourceRole already bound to that pipeline - no iam:PassRole needed.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanModifyCode {'pipeline': None}
{'ec2resource': None} ExecutesAs {'role': None}
where
node_type(?pipeline) ==
Workflow
?pipeline.provider_type == 'AWS::DataPipeline::Pipeline'
?ec2resource is the EC2/EMR resource node of ?pipeline (ContainsResourceReference or structural link)
?role.privilege_level >= ?principal.privilege_level # only emit when escalation is possible
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
{'principal': None}
CanTrigger {'pipeline': None}
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | ?principal |
| target | ?role |
| permissions | datapipeline:PutPipelineDefinition datapipeline:ActivatePipeline |
| conditions | iam_permission condition_expression trigger_exists |
| state logic | ACTIVE when principal has EFFECTIVE datapipeline:PutPipelineDefinition on the pipeline AND a run path exists (datapipeline:ActivatePipeline matched via optional[0], or an active schedule runs the pipeline). CONDITIONAL (trigger_exists) when no run path is confirmed. BLOCKED by a deny on datapipeline:PutPipelineDefinition or datapipeline:ActivatePipeline. Inherits weakest state from CanModifyCode and ExecutesAs contributors. Only emit when ?role is more privileged than ?principal (escalation); a lateral same-privilege existing resourceRole is still code execution but not escalation — emit CanExecuteAs without CanEscalateTo in that case. |
Narrative
{principal.name} can rewrite the activity commands in existing pipeline {pipeline.name} (datapipeline:PutPipelineDefinition) and activate it (datapipeline:ActivatePipeline). The EC2 resource node in the pipeline already runs as {role.name}; injecting a credential-exfiltrating command and running it yields {role.name}'s credentials via IMDS - without needing iam:PassRole.
Raw rule rules/derived/aws/datapipeline.yaml
id: aws-datapipeline-execute-as-existing-def
emits: CanExecuteAs
description: "Inject shell commands into an existing pipeline definition and activate it to execute as\
\ the resourceRole already bound to that pipeline \u2014 no iam:PassRole needed."
match:
- - principal: null
- CanModifyCode
- pipeline: null
- - ec2resource: null
- ExecutesAs
- role: null
where:
- node_type(?pipeline) == Workflow
- ?pipeline.provider_type == 'AWS::DataPipeline::Pipeline'
- ?ec2resource is the EC2/EMR resource node of ?pipeline (ContainsResourceReference or structural link)
- '?role.privilege_level >= ?principal.privilege_level # only emit when escalation is possible'
optional:
- - principal: null
- CanTrigger
- pipeline: null
emit:
source_type: Identity
target_type: Identity
source: ?principal
target: ?role
permissions:
- datapipeline:PutPipelineDefinition
- datapipeline:ActivatePipeline
conditions:
- iam_permission
- condition_expression
- trigger_exists
state_logic: "ACTIVE when principal has EFFECTIVE datapipeline:PutPipelineDefinition on the pipeline\
\ AND a run path exists (datapipeline:ActivatePipeline matched via optional[0], or an active schedule\
\ runs the pipeline). CONDITIONAL (trigger_exists) when no run path is confirmed. BLOCKED by a deny\
\ on datapipeline:PutPipelineDefinition or datapipeline:ActivatePipeline. Inherits weakest state from\
\ CanModifyCode and ExecutesAs contributors. Only emit when ?role is more privileged than ?principal\
\ (escalation); a lateral same-privilege existing resourceRole is still code execution but not escalation\
\ \u2014 emit CanExecuteAs without CanEscalateTo in that case."
confidence: min(contributing_confidences) * 0.95
derived_from:
- ?principal CanModifyCode ?pipeline (aws-datapipeline-put-definition-code)
- ?ec2resource ExecutesAs ?role (aws-datapipeline-executes-as-resource-role)
- "can-execute-as:execute-as-via-code-modify (linchpin \u2014 cited not re-derived)"
false_positive_note: "No iam:PassRole is required because the role is already bound to the pipeline\
\ definition \u2014 the attacker exploits the EXISTING resourceRole binding. The key gating conditions\
\ are: (1) datapipeline:PutPipelineDefinition is EFFECTIVE on the specific pipeline (honor tag/creator/workerGroup\
\ conditions); (2) a run path (ActivatePipeline or active schedule) is established. A pipeline in\
\ a permanently INACTIVE state with no active schedule and where ActivatePipeline is denied yields\
\ CONDITIONAL(trigger_exists), never ACTIVE. Dedupes with rule 8 on (CanExecuteAs, ?principal, ?role)\
\ for the case where the attacker both has PassRole and is targeting the same existing role \u2014\
\ rule 8 is ACTIVE (has both PassRole and PutPipelineDefinition); rule 9 is ACTIVE too (existing binding);\
\ deduplicate, emit once with higher-confidence evidence from rule 8."
narrative: "{principal.name} can rewrite the activity commands in existing pipeline {pipeline.name}\
\ (datapipeline:PutPipelineDefinition) and activate it (datapipeline:ActivatePipeline). The EC2 resource\
\ node in the pipeline already runs as {role.name}; injecting a credential-exfiltrating command and\
\ running it yields {role.name}'s credentials via IMDS \u2014 without needing iam:PassRole."