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.

derived aws emits CanExecuteAs

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 typeIdentity
target typeIdentity
source?principal
target?role
permissionsdatapipeline:PutPipelineDefinition datapipeline:ActivatePipeline
conditionsiam_permission condition_expression trigger_exists
state logicACTIVE 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."
move · open · esc close