Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions detections/cloud/aws_repeated_cloudwatch_logs_read_operations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: AWS Repeated CloudWatch Logs Read Operations
id: 3f0ba9e1-c6cf-4317-a6bc-c4bcab2b5a06
version: 1
creation_date: '2026-09-21'
modification_date: '2026-09-21'
author: Maria Jose Erquiaga, Splunk
status: production
type: TTP
description: >-
The following analytic detects a high volume of Amazon CloudWatch Logs read
operations performed by the same AWS principal and account within a
five-minute period. It identifies more than 500 DescribeLogGroups,
DescribeLogStreams, DownloadLogEvents, FilterLogEvents, GetLogEvents, or
GetQueryResults API calls. This activity may indicate automated discovery or
collection of operational, application, or security telemetry from
CloudWatch Logs.
data_source:
- AWS CloudTrail
search: |-
`cloudtrail` eventSource="logs.amazonaws.com"
eventName IN (
"DescribeLogGroups",
"DescribeLogStreams",
"DownloadLogEvents",
"FilterLogEvents",
"GetLogEvents",
"GetQueryResults"
)
| eval user='userIdentity.principalId'
| eval vendor_account=coalesce(
vendor_account,
recipientAccountId,
'userIdentity.accountId'
)
| eval role_name='userIdentity.sessionContext.sessionIssuer.userName'
| eval signature=coalesce(signature, eventName)
| eval src=coalesce(src, sourceIPAddress)
| eval user_agent=coalesce(user_agent, userAgent)
| eval vendor_region=coalesce(vendor_region, awsRegion)
| where isnotnull(user) AND len(trim(user)) > 0
| sort 0 _time
| streamstats time_window=5m count AS event_count
by user vendor_account
Comment on lines +42 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Deduplicate CloudTrail IDs before counting operations

In environments where CloudTrail events are delivered or ingested more than once, this counts every copy as a separate API operation because no eventID deduplication occurs. A principal with at most 500 actual calls can therefore cross the threshold and generate a false alert, contradicting the implementation guidance that promises unique identifiers are counted to reduce duplicate-ingestion effects. Deduplicate by eventID before streamstats (while retaining events without an ID), or remove that guarantee and explicitly accept duplicate-sensitive counts.

Useful? React with 👍 / 👎.

| where event_count > 500
| stats
max(event_count) AS event_count
min(_time) AS firstTime
max(_time) AS lastTime
values(signature) AS api_operations
values(role_name) AS role_name
values(src) AS src
values(user_agent) AS user_agent
values(vendor_region) AS vendor_region
by user vendor_account
| `security_content_ctime(firstTime)`
| `security_content_ctime(lastTime)`
| `aws_repeated_cloudwatch_logs_read_operations_filter`
how_to_implement: >-
The Splunk Add-on for AWS is required to collect AWS CloudTrail events.
Configure CloudTrail to capture management Read events and ingest them with
the aws:cloudtrail sourcetype. The tested CloudWatch Logs operations are
recorded as management events, so a CloudWatch Logs data-event selector is
not required. The analytic uses a rolling five-minute window and counts
Comment on lines +60 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enable data events for GetLogEvents and FilterLogEvents

When a deployment follows this guidance and collects only management Read events, CloudTrail will not ingest GetLogEvents or FilterLogEvents: AWS classifies these CloudWatch Logs log-group operations as data events. Consequently, the documented configuration misses the primary log-retrieval activity—and specifically the GetLogEvents activity used by this analytic's true-positive test. Require an advanced data-event selector for AWS::Logs::LogGroup instead of stating that no selector is needed.

Useful? React with 👍 / 👎.

unique CloudTrail event identifiers to reduce duplicate-ingestion effects.
known_false_positives: >-
Automated log analytics, SIEM pipelines, incident-response tooling, backup
processes, and administrative troubleshooting may continuously read
CloudWatch Logs and generate a high volume of matching operations. The
prevalence of this activity has not yet been evaluated against broader
customer telemetry. Review the principal, account, source addresses, user
agents, Regions, and accessed CloudWatch Logs resources before escalating.
references:
- https://attack.mitre.org/techniques/T1530/
- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogGroups.html
- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_DescribeLogStreams.html
- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_FilterLogEvents.html
- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetLogEvents.html
- https://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/API_GetQueryResults.html
drilldown_searches:
- name: View CloudWatch Logs read operations for - "$user$"
search: '%original_detection_search% | search user="$user$" vendor_account="$vendor_account$"'
earliest_offset: $info_min_time$
latest_offset: $info_max_time$
- name: View risk events for the last 7 days for - "$user$"
search: '| from datamodel Risk.All_Risk | search normalized_risk_object IN ("$user$") | stats count min(_time) as firstTime max(_time) as lastTime values(search_name) as "Search Name" values(risk_message) as "Risk Message" values(analyticstories) as "Analytic Stories" values(annotations._all) as "Annotations" values(annotations.mitre_attack.mitre_tactic) as "ATT&CK Tactics" by normalized_risk_object | `security_content_ctime(firstTime)` | `security_content_ctime(lastTime)`'
earliest_offset: 7d
latest_offset: "0"
finding:
title: AWS principal $user$ performed $event_count$ CloudWatch Logs read operations
entity:
field: user
type: user
score: 50
threat_objects:
- field: src
type: ip_address
analytic_story:
- Data Exfiltration
asset_type: AWS Account
mitre_attack_id:
- T1530
product:
- Splunk Enterprise
- Splunk Enterprise Security
- Splunk Cloud
category: cloud
security_domain: threat
tests:
- name: True Positive Test
attack_data:
- data: https://media.githubusercontent.com/media/splunk/attack_data/master/datasets/attack_techniques/T1530/aws_cloudwatch_logs_high_volume_retrieval/aws_cloudwatch_logs_high_volume_retrieval.json
sourcetype: aws:cloudtrail
source: aws_cloudtrail
test_type: unit
Loading