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.

derived aws emits CanTrigger

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'principal': None} CanWriteData {'stream': None} {'stream': None} CanTrigger {'consumer': None}

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 typeIdentity
target typeCompute
source?principal
target?consumer
permissionskinesis:PutRecord kinesis:PutRecords
conditionstrigger_exists service_state
state logicACTIVE 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.'
move · open · esc close