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.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
HasPermission {'ledger': None}
{'principal': None} CanPassIdentity {'role': None}
where
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: 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."