aws-sf-cross-account-execution
A cross-account principal with states:StartExecution on a state machine in another account can directly trigger an execution in that account with an attacker-influenced payload.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'external': None}
CanInvoke {'state_machine': None}
{'state_machine': None} ExecutesAs {'role': None}
where
?external.account != ?state_machine.account
?external has effective states:StartExecution on the state machine ARN in the remote account (cross-account IAM grant)
emit
| source type | Identity |
|---|---|
| target type | Workflow |
| source | ?external |
| target | ?state_machine |
| permissions | states:StartExecution |
| conditions | iam_permission resource_policy |
| state logic | ACTIVE when the cross-account IAM grant is unconditional (the external principal's identity policy grants states:StartExecution on the remote state machine ARN with no restricting conditions). CONDITIONAL(resource_policy) when the grant requires conditions (aws:SourceAccount, aws:PrincipalOrgID, aws:VpcSourceIp) the attacker must satisfy. Starting an execution in account B runs the state machine's execution role in B — the caller does not receive B's execution role credentials directly, but their input payload influences the entire execution, including downstream Task state parameters and any data the execution writes to S3/SQS/DynamoDB. |
Narrative
{external.name} (account {external.account}) can start an execution of state machine {state_machine.name} in account {state_machine.account} (states:StartExecution cross-account), supplying the input payload that drives the entire execution as {role.name}.
Raw rule rules/derived/aws/stepfunctions.yaml
id: aws-sf-cross-account-execution
emits: CanInvoke
description: A cross-account principal with states:StartExecution on a state machine in another account
can directly trigger an execution in that account with an attacker-influenced payload.
match:
- - external: null
- CanInvoke
- state_machine: null
- - state_machine: null
- ExecutesAs
- role: null
where:
- ?external.account != ?state_machine.account
- ?external has effective states:StartExecution on the state machine ARN in the remote account (cross-account
IAM grant)
emit:
source_type: Identity
target_type: Workflow
source: ?external
target: ?state_machine
permissions:
- states:StartExecution
conditions:
- iam_permission
- resource_policy
state_logic: "ACTIVE when the cross-account IAM grant is unconditional (the external principal's identity\
\ policy grants states:StartExecution on the remote state machine ARN with no restricting conditions).\
\ CONDITIONAL(resource_policy) when the grant requires conditions (aws:SourceAccount, aws:PrincipalOrgID,\
\ aws:VpcSourceIp) the attacker must satisfy. Starting an execution in account B runs the state machine's\
\ execution role in B \u2014 the caller does not receive B's execution role credentials directly,\
\ but their input payload influences the entire execution, including downstream Task state parameters\
\ and any data the execution writes to S3/SQS/DynamoDB."
confidence: min(contributing_confidences) * 0.85
derived_from:
- <CanInvoke edge_id ?external -> ?state_machine>
- <ExecutesAs edge_id ?state_machine -> ?role>
false_positive_note: "Cross-account StartExecution requires the CALLER's identity policy to allow states:StartExecution\
\ on the REMOTE ARN \u2014 Step Functions does not support resource-based policies on state machines,\
\ so this is purely IAM-identity-policy-driven. The caller triggers execution with an attacker-controlled\
\ input payload; the execution runs the execution role in the target account. The caller does not\
\ obtain that role's credentials directly. This is CanInvoke (direct invocation from another account)\
\ \u2014 cross-account entry only materializes if combined with CanModifyCode (B1) for the definition-modify\
\ path or via messaging-chains for event triggers. Do NOT downgrade to CanEnterAccount; that is reserved\
\ for cases where the caller obtains credentials in the target account."
narrative: '{external.name} (account {external.account}) can start an execution of state machine {state_machine.name}
in account {state_machine.account} (states:StartExecution cross-account), supplying the input payload
that drives the entire execution as {role.name}.'