aws-qldb-stream-journal-kinesis-exfiltrate

qldb:StreamJournalToKinesis + iam:PassRole (for a role with kinesis:PutRecord) allows the caller to continuously stream the entire QLDB journal - all committed document revisions across all tables - to a Kinesis Data Stream, including cross-account streams.

derived aws emits CanExfiltrate

match

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

{'principal': None} HasPermission {'ledger': None} {'principal': None} CanPassIdentity {'role': None}

where

node_type(?ledger) == NoSQLDatabase ?ledger.provider_type == 'AWS::QLDB::Ledger' effective_action(?principal, ?ledger) includes 'qldb:StreamJournalToKinesis' ?role.trust_policy includes 'qldb.amazonaws.com' as a trusted principal effective_action(?principal, ?role) includes 'iam:PassRole' iam:PassRole for ?role permits iam:PassedToService including 'qldb.amazonaws.com' (or unconditioned) effective_action(?principal, ?role) OR effective_action(?role, '*') includes 'kinesis:PutRecord' OR 'kinesis:PutRecords' on at least one stream

emit

source typeIdentity
target typeNoSQLDatabase
source?principal
target?ledger
permissionsqldb:StreamJournalToKinesis iam:PassRole
conditionsiam_permission role_compatibility resource_policy
state logicif effective 'qldb:StreamJournalToKinesis' on ?ledger AND ?principal CanPassIdentity ?role AND ?role trusts qldb.amazonaws.com AND effective 'kinesis:PutRecord' OR 'kinesis:PutRecords' on ?role: if the kinesis:PutRecord scope is confirmed to be cross-account OR attacker-controlled: ACTIVE — journal data continuously leaves the account boundary. elif the stream is confirmed same-account and under normal Kinesis access policies: do NOT emit CanExfiltrate (data stays in-account). The streamed journal records are now Kinesis data; their read access is governed by aws:kinesis CanReadData rules (cross-reference the Kinesis stream node and the role's kinesis:GetRecords permission). elif the target stream/account destination is unknown: CONDITIONAL(resource_policy) — gate on whether the Kinesis stream policy permits access from outside (cross-account write scope). else: do not emit.

Narrative

{principal.name} can start a QLDB journal stream (qldb:StreamJournalToKinesis) on {ledger.name} and pass a service role (iam:PassRole) that QLDB assumes to continuously write all journal blocks - including every committed document revision across all tables - to a Kinesis Data Stream. If the target stream is attacker-controlled or cross-account, this constitutes continuous data exfiltration of the entire {ledger.name} ledger.

Raw rule rules/derived/aws/qldb.yaml

id: aws-qldb-stream-journal-kinesis-exfiltrate
emits: CanExfiltrate
description: "qldb:StreamJournalToKinesis + iam:PassRole (for a role with kinesis:PutRecord) allows the\
  \ caller to continuously stream the entire QLDB journal \u2014 all committed document revisions across\
  \ all tables \u2014 to a Kinesis Data Stream, including cross-account streams."
match:
- - principal: null
  - HasPermission
  - ledger: null
- - principal: null
  - CanPassIdentity
  - role: null
where:
- node_type(?ledger) == NoSQLDatabase
- ?ledger.provider_type == 'AWS::QLDB::Ledger'
- effective_action(?principal, ?ledger) includes 'qldb:StreamJournalToKinesis'
- ?role.trust_policy includes 'qldb.amazonaws.com' as a trusted principal
- effective_action(?principal, ?role) includes 'iam:PassRole'
- iam:PassRole for ?role permits iam:PassedToService including 'qldb.amazonaws.com' (or unconditioned)
- effective_action(?principal, ?role) OR effective_action(?role, '*') includes 'kinesis:PutRecord' OR
  'kinesis:PutRecords' on at least one stream
emit:
  source_type: Identity
  target_type: NoSQLDatabase
  source: ?principal
  target: ?ledger
  permissions:
  - qldb:StreamJournalToKinesis
  - iam:PassRole
  conditions:
  - iam_permission
  - role_compatibility
  - resource_policy
  state_logic: "if effective 'qldb:StreamJournalToKinesis' on ?ledger AND ?principal CanPassIdentity ?role\
    \ AND ?role trusts qldb.amazonaws.com AND effective 'kinesis:PutRecord' OR 'kinesis:PutRecords' on\
    \ ?role:\n  if the kinesis:PutRecord scope is confirmed to be cross-account OR attacker-controlled:\n\
    \    ACTIVE \u2014 journal data continuously leaves the account boundary.\n  elif the stream is confirmed\
    \ same-account and under normal Kinesis access policies:\n    do NOT emit CanExfiltrate (data stays\
    \ in-account). The streamed journal records\n    are now Kinesis data; their read access is governed\
    \ by aws:kinesis CanReadData rules\n    (cross-reference the Kinesis stream node and the role's kinesis:GetRecords\
    \ permission).\n  elif the target stream/account destination is unknown:\n    CONDITIONAL(resource_policy)\
    \ \u2014 gate on whether the Kinesis stream policy permits\n    access from outside (cross-account\
    \ write scope).\nelse: do not emit."
  confidence: contributing_confidence * 0.90
  derived_from:
  - ?principal CanPassIdentity ?role
  - qldb:StreamJournalToKinesis EFFECTIVE on ?ledger
  false_positive_note: "Requires BOTH qldb:StreamJournalToKinesis on the ledger AND iam:PassRole on a\
    \ role that trusts qldb.amazonaws.com AND the role has kinesis:PutRecord or kinesis:PutRecords on\
    \ at least one stream. A role passable only to other services (e.g., ec2.amazonaws.com) does NOT enable\
    \ QLDB streaming \u2014 honor iam:PassedToService conditions and role trust policy principal. The\
    \ stream is continuous; once started, all committed journal blocks are streamed until the stream is\
    \ deleted or stopped. Do not emit ACTIVE when only one or two of the three required conditions are\
    \ present. When the target stream is confirmed cross-account or attacker-controlled, this is ACTIVE\
    \ exfiltration; when the stream/account destination is unknown, emit CONDITIONAL(resource_policy).\
    \ When the target stream is confirmed same-account with normal Kinesis access policies, do NOT emit\
    \ CanExfiltrate \u2014 the data stays in-account. The streamed journal records are Kinesis data; their\
    \ read access is governed by aws:kinesis CanReadData rules. Cross-reference the Kinesis stream node\
    \ and the role's kinesis:GetRecords permission to determine whether CanReadData applies."
  narrative: "{principal.name} can start a QLDB journal stream (qldb:StreamJournalToKinesis) on {ledger.name}\
    \ and pass a service role (iam:PassRole) that QLDB assumes to continuously write all journal blocks\
    \ \u2014 including every committed document revision across all tables \u2014 to a Kinesis Data Stream.\
    \ If the target stream is attacker-controlled or cross-account, this constitutes continuous data exfiltration\
    \ of the entire {ledger.name} ledger."
move · open · esc close