aws-kinesis-put-triggers-consumer
kinesis:PutRecord(s) to a stream with an active Lambda ESM triggers the consumer function with an attacker-influenced record payload.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?stream) ==
GenericMessaging
node_class(?consumer) == Compute or node_type(?consumer) == Workflow
?stream.provider_type == 'AWS::Kinesis::Stream'
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
?stream ESM binding has no FilterCriteria (or attacker payload satisfies it)
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?consumer |
| permissions | kinesis:PutRecord kinesis:PutRecords |
| conditions | trigger_exists service_state |
| state logic | ACTIVE when the ESM binding between ?stream and ?consumer is confirmed ENABLED and the consumer is invocable (Lambda function exists and is not throttled to 0). CONDITIONAL(trigger_exists) when the ESM is DISABLED or its state is unknown. CONDITIONAL(condition_expression) when the ESM has a FilterCriteria expression the attacker payload may not satisfy. Inherit the weakest state from the contributing CanWriteData (a resource-policy/SCP- capped publish downgrades this) and the CanTrigger binding edge. A BLOCKED deny on kinesis:PutRecord or lambda:InvokeFunction blocks the chain. |
Narrative
{principal.name} can write records to the Kinesis stream {stream.name} (kinesis:PutRecord / kinesis:PutRecords). An active Lambda event-source- mapping delivers those records to {consumer.name}; a crafted record therefore triggers {consumer.name} to run with an attacker-influenced payload. messaging-chains then collapses this with {consumer.name}'s ExecutesAs binding to produce CanExecuteAs over the function's execution role.
Raw rule rules/derived/aws/kinesis.yaml
id: aws-kinesis-put-triggers-consumer
emits: CanTrigger
description: kinesis:PutRecord(s) to a stream with an active Lambda ESM triggers the consumer function
with an attacker-influenced record payload.
match:
- - principal: null
- CanWriteData
- stream: null
- - stream: null
- CanTrigger
- consumer: null
where:
- node_type(?stream) == GenericMessaging
- node_class(?consumer) == Compute or node_type(?consumer) == Workflow
- ?stream.provider_type == 'AWS::Kinesis::Stream'
optional:
- ?stream ESM binding has no FilterCriteria (or attacker payload satisfies it)
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?consumer
permissions:
- kinesis:PutRecord
- kinesis:PutRecords
conditions:
- trigger_exists
- service_state
state_logic: ACTIVE when the ESM binding between ?stream and ?consumer is confirmed ENABLED and the
consumer is invocable (Lambda function exists and is not throttled to 0). CONDITIONAL(trigger_exists)
when the ESM is DISABLED or its state is unknown. CONDITIONAL(condition_expression) when the ESM has
a FilterCriteria expression the attacker payload may not satisfy. Inherit the weakest state from the
contributing CanWriteData (a resource-policy/SCP- capped publish downgrades this) and the CanTrigger
binding edge. A BLOCKED deny on kinesis:PutRecord or lambda:InvokeFunction blocks the chain.
confidence: min(contributing_confidences) * 0.9
derived_from:
- ?principal CanWriteData ?stream (kinesis:PutRecord / kinesis:PutRecords)
- ?stream CanTrigger ?consumer (Lambda ESM binding, aws-lambda-event-source-trigger)
- messaging-chains:messaging-publish-triggers-consumer (provider-agnostic roll-up)
false_positive_note: "Three gating conditions all required for ACTIVE: (1) the principal has EFFECTIVE\
\ kinesis:PutRecord or kinesis:PutRecords on the stream ARN \u2014 confirm both the identity policy\
\ and any stream resource policy deny; a resource policy with a deny on kinesis:PutRecord blocks even\
\ if the identity policy allows. (2) The Lambda ESM State is ENABLED \u2014 a DISABLED ESM means records\
\ are not delivered to the function; downgrade to CONDITIONAL(trigger_exists). (3) If the ESM has\
\ FilterCriteria, only records matching the JSON filter trigger the function; downgrade to CONDITIONAL(condition_expression)\
\ unless the attacker can craft a conforming payload (often possible for simple key- value matchers,\
\ but not guaranteed). Do not emit ACTIVE for a disabled mapping or a filter the attacker demonstrably\
\ cannot satisfy. This CanTrigger feeds messaging-chains rule 2 (messaging-trigger-executes-as) for\
\ the CanExecuteAs consequence \u2014 that roll-up is messaging-chains', not re-derived."
narrative: '{principal.name} can write records to the Kinesis stream {stream.name} (kinesis:PutRecord
/ kinesis:PutRecords). An active Lambda event-source- mapping delivers those records to {consumer.name};
a crafted record therefore triggers {consumer.name} to run with an attacker-influenced payload. messaging-chains
then collapses this with {consumer.name}''s ExecutesAs binding to produce CanExecuteAs over the function''s
execution role.'