aws-cloudtrail-lookup-events
Principal with cloudtrail:LookupEvents can query 90 days of account-wide management-event history without S3 access, exposing IAM principal names, access-key IDs, resource ARNs, and error codes - useful for recon and lateral movement planning.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?account) ==
Account
effective_action(?principal, ?account) includes 'cloudtrail:LookupEvents'
emit
| source type | Identity |
|---|---|
| target type | Account |
| source | ?principal |
| target | ?account |
| permissions | cloudtrail:LookupEvents |
| conditions | iam_permission scp_or_org_policy |
| state logic | ACTIVE when the principal holds cloudtrail:LookupEvents with no effective deny. LookupEvents operates account-wide and is not scoped to a specific trail in IAM (the IAM resource is always *). BLOCKED only when an SCP or boundary explicitly denies the action. This edge is emitted once per account (per principal-account pair), not once per trail. |
Narrative
{principal.name} can call cloudtrail:LookupEvents to search 90 days of management-event history in {account.name} - revealing IAM principal names, access-key IDs used for API calls, resource ARNs, source IPs, and error codes. This is a recon capability for lateral movement planning; it grants no resource access.
Raw rule rules/derived/aws/cloudtrail.yaml
id: aws-cloudtrail-lookup-events
emits: CanRead
description: "Principal with cloudtrail:LookupEvents can query 90 days of account-wide management-event\
\ history without S3 access, exposing IAM principal names, access-key IDs, resource ARNs, and error\
\ codes \u2014 useful for recon and lateral movement planning."
match:
- - principal: null
- HasPermission
- account: null
where:
- node_type(?account) == Account
- effective_action(?principal, ?account) includes 'cloudtrail:LookupEvents'
emit:
source_type: Identity
target_type: Account
source: ?principal
target: ?account
permissions:
- cloudtrail:LookupEvents
conditions:
- iam_permission
- scp_or_org_policy
state_logic: ACTIVE when the principal holds cloudtrail:LookupEvents with no effective deny. LookupEvents
operates account-wide and is not scoped to a specific trail in IAM (the IAM resource is always *).
BLOCKED only when an SCP or boundary explicitly denies the action. This edge is emitted once per account
(per principal-account pair), not once per trail.
confidence: 0.9
derived_from:
- HasPermission(?principal, cloudtrail:LookupEvents, ?account)
false_positive_note: "RECON primitive only \u2014 LookupEvents surfaces metadata about past API calls\
\ (who called what, when, from where, with which access key) but does NOT expose raw credentials or\
\ session tokens. It cannot be used to access other resources. Do NOT chain CanRead(account) to CanAssume,\
\ CanImpersonate, or any access edge without a separate, evidenced permission grant. Note: LookupEvents\
\ returns management events from the CURRENT account's event history; it does NOT reach org-level\
\ data or other accounts' trails. Analysts should note that error-code fields (AccessDenied) in returned\
\ events expose which actions the caller does NOT have, giving attackers negative-space recon. This\
\ edge targets the Account boundary node (not a specific trail) because LookupEvents queries the entire\
\ account's audit history regardless of trail configuration."
narrative: "{principal.name} can call cloudtrail:LookupEvents to search 90 days of management-event\
\ history in {account.name} \u2014 revealing IAM principal names, access-key IDs used for API calls,\
\ resource ARNs, source IPs, and error codes. This is a recon capability for lateral movement planning;\
\ it grants no resource access."