aws-sf-execute-as-via-direct-invoke
Directly invoke a state machine (states:StartExecution) with an attacker-supplied input payload to execute as the machine's execution role.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanInvoke {'state_machine': None}
{'state_machine': None} ExecutesAs {'role': None}
where
node_type(?state_machine) ==
Workflow
emit
| source type | Identity |
|---|---|
| target type | MachineIdentity |
| source | ?principal |
| target | ?role |
| permissions | states:StartExecution |
| conditions | iam_permission |
| state logic | ACTIVE when the principal has effective states:StartExecution (or states:StartSyncExecution for Express workflows) on the state machine. The attacker directly invokes an execution with an attacker-controlled input payload — the execution runs the entire workflow definition as ?role, granting the attacker the role's identity for all Task state calls. Inherit state from contributing CanInvoke (not gated on a trigger existing — the principal supplies the trigger directly). BLOCKED if the principal lacks CanInvoke or the role is deleted. |
Narrative
{principal.name} can directly start an execution of state machine {state_machine.name} (states:StartExecution), supplying the input payload and causing the entire execution to run as {role.name}.
Raw rule rules/derived/aws/stepfunctions.yaml
id: aws-sf-execute-as-via-direct-invoke
emits: CanExecuteAs
description: Directly invoke a state machine (states:StartExecution) with an attacker-supplied input payload
to execute as the machine's execution role.
match:
- - principal: null
- CanInvoke
- state_machine: null
- - state_machine: null
- ExecutesAs
- role: null
where:
- node_type(?state_machine) == Workflow
emit:
source_type: Identity
target_type: MachineIdentity
source: ?principal
target: ?role
permissions:
- states:StartExecution
conditions:
- iam_permission
state_logic: "ACTIVE when the principal has effective states:StartExecution (or states:StartSyncExecution\
\ for Express workflows) on the state machine. The attacker directly invokes an execution with an\
\ attacker-controlled input payload \u2014 the execution runs the entire workflow definition as ?role,\
\ granting the attacker the role's identity for all Task state calls. Inherit state from contributing\
\ CanInvoke (not gated on a trigger existing \u2014 the principal supplies the trigger directly).\
\ BLOCKED if the principal lacks CanInvoke or the role is deleted."
confidence: min(contributing_confidences) * 0.9
derived_from:
- <CanInvoke edge_id ?principal -> ?state_machine>
- <ExecutesAs edge_id ?state_machine -> ?role>
false_positive_note: "Only ACTIVE for direct invocation (CanInvoke matched); a trigger-based execution\
\ without direct invocation capability is CONDITIONAL(trigger_exists) and handled by the messaging-chains\
\ bridge. Dedupes with messaging-trigger-executes-as on (CanExecuteAs, ?principal, ?role) \u2014 this\
\ rule covers the CanInvoke path, messaging-chains covers the event-trigger path (which targets Messaging\
\ sources, not the Workflow directly). Do not double-count weight. The identity hierarchy consequence\
\ (escalation if ?role is more privileged) is produced by can-control \u2014 cite, do not re-derive."
narrative: '{principal.name} can directly start an execution of state machine {state_machine.name} (states:StartExecution),
supplying the input payload and causing the entire execution to run as {role.name}.'