aws-ebs-direct-api-read
ebs:ListSnapshotBlocks + ebs:GetSnapshotBlock give block-level read of snapshot data without mounting - no EC2 footprint required. This rule matches on direct IAM capability evaluation, not on materialized HasPermission edges (which may not exist for wildcard-scoped ebs: permissions).
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?snapshot) ==
Snapshot
?principal has EFFECTIVE ebs:ListSnapshotBlocks (scoped to ?snapshot ARN, or Resource: *, or ebs:* wildcard)
?principal has EFFECTIVE ebs:GetSnapshotBlock (scoped to ?snapshot ARN, or Resource: *, or ebs:* wildcard)
emit
| source type | Identity |
|---|---|
| target type | Snapshot |
| source | ?principal |
| target | ?snapshot |
| permissions | ebs:ListSnapshotBlocks ebs:GetSnapshotBlock |
| conditions | iam_permission key_permission |
| state logic | ACTIVE when both ebs:ListSnapshotBlocks and ebs:GetSnapshotBlock are effective on ?snapshot AND (snapshot is unencrypted OR caller also holds kms:Decrypt on the wrapping CMK). CONDITIONAL(key_permission) when the snapshot is CMK-encrypted and kms:Decrypt is unconfirmed — cite credential-chains.yaml:decrypt-gates-secret-read for upgrade to ACTIVE once decrypt is confirmed. The rule engine must handle both materialized HasPermission edges (from explicit normalization) AND direct IAM policy evaluation for principals with broad ebs:* or Resource:* grants. |
Narrative
{principal.name} holds ebs:ListSnapshotBlocks and ebs:GetSnapshotBlock on {snapshot.name}, enabling full block-level read of the snapshot's disk contents without mounting a volume - a low-footprint data exfiltration path that bypasses EC2 entirely.
Raw rule rules/derived/aws/ebs.yaml
id: aws-ebs-direct-api-read
emits: CanReadData
description: "ebs:ListSnapshotBlocks + ebs:GetSnapshotBlock give block-level read of snapshot data without\
\ mounting \u2014 no EC2 footprint required. This rule matches on direct IAM capability evaluation,\
\ not on materialized HasPermission edges (which may not exist for wildcard-scoped ebs: permissions)."
match:
- - principal: null
- HasPermission
- snapshot: null
where:
- node_type(?snapshot) == Snapshot
- '?principal has EFFECTIVE ebs:ListSnapshotBlocks (scoped to ?snapshot ARN, or Resource: *, or ebs:*
wildcard)'
- '?principal has EFFECTIVE ebs:GetSnapshotBlock (scoped to ?snapshot ARN, or Resource: *, or ebs:* wildcard)'
emit:
source_type: Identity
target_type: Snapshot
source: ?principal
target: ?snapshot
permissions:
- ebs:ListSnapshotBlocks
- ebs:GetSnapshotBlock
conditions:
- iam_permission
- key_permission
state_logic: "ACTIVE when both ebs:ListSnapshotBlocks and ebs:GetSnapshotBlock are effective on ?snapshot\
\ AND (snapshot is unencrypted OR caller also holds kms:Decrypt on the wrapping CMK). CONDITIONAL(key_permission)\
\ when the snapshot is CMK-encrypted and kms:Decrypt is unconfirmed \u2014 cite credential-chains.yaml:decrypt-gates-secret-read\
\ for upgrade to ACTIVE once decrypt is confirmed. The rule engine must handle both materialized HasPermission\
\ edges (from explicit normalization) AND direct IAM policy evaluation for principals with broad ebs:*\
\ or Resource:* grants."
confidence: 0.92
derived_from:
- Direct IAM policy evaluation of ebs:ListSnapshotBlocks + ebs:GetSnapshotBlock
false_positive_note: "BOTH ebs:ListSnapshotBlocks AND ebs:GetSnapshotBlock are required; holding only\
\ one is insufficient for practical exploitation. The rule engine must evaluate two cases: (1) Explicit\
\ HasPermission edge exists in the graph for a specific snapshot (from explicit normalizer running\
\ IAM evaluator); (2) Principal holds ebs:ListSnapshotBlocks or ebs:GetSnapshotBlock with Resource:\
\ * or snapshot ARNs that may not have materialized edges \u2014 the rule engine must fall back to\
\ direct IAM evaluation to emit the edge. For CMK-encrypted snapshots, GetSnapshotBlock returns 400\
\ (InvalidParameter) unless the caller holds kms:Decrypt on the encryption key \u2014 strictly CONDITIONAL(key_permission)\
\ without proof of decrypt. The ebs: namespace is separate from the ec2: namespace: an ec2:Describe*\
\ grant does NOT imply ebs:GetSnapshotBlock. Honor per-snapshot ARN scoping and SCPs."
narrative: "{principal.name} holds ebs:ListSnapshotBlocks and ebs:GetSnapshotBlock on {snapshot.name},\
\ enabling full block-level read of the snapshot's disk contents without mounting a volume \u2014\
\ a low-footprint data exfiltration path that bypasses EC2 entirely."