Techniques
Sample rules
AWS S3 Rapid Bucket Posture API Calls from a Single Principal
- source: elastic
- technicques:
- T1526
- T1530
- T1580
- T1619
Description
Identifies when the same AWS principal, from the same source IP, successfully invokes read-only S3 control-plane APIs that reveal bucket posture across many buckets in a short period. This pattern can indicate automated reconnaissance or security scanning, similar to CSPM tools and post-compromise enumeration. The rule excludes AWS service principals, requires programmatic-style sessions (not Management Console credentials), and requires populated resource and identity fields so nulls do not skew cardinality.
Detection logic
from logs-aws.cloudtrail-* metadata _id, _version, _index
| eval Esql.time_window_date_trunc = date_trunc(10 seconds, @timestamp)
| where
event.dataset == "aws.cloudtrail"
and event.provider == "s3.amazonaws.com"
and event.outcome == "success"
and event.action in (
"GetBucketAcl",
"GetBucketPublicAccessBlock",
"GetBucketPolicy",
"GetBucketPolicyStatus",
"GetBucketVersioning"
)
and aws.cloudtrail.user_identity.type != "AWSService"
and source.ip IS NOT NULL
and aws.cloudtrail.resources.arn IS NOT NULL
and aws.cloudtrail.user_identity.arn IS NOT NULL
and aws.cloudtrail.session_credential_from_console IS NULL
| keep
@timestamp,
Esql.time_window_date_trunc,
event.action,
aws.cloudtrail.user_identity.arn,
aws.cloudtrail.user_identity.type,
aws.cloudtrail.user_identity.access_key_id,
source.ip,
aws.cloudtrail.resources.arn,
cloud.account.id,
cloud.region,
user_agent.original,
source.as.organization.name,
data_stream.namespace
| stats
Esql.bucket_arn_count_distinct = count_distinct(aws.cloudtrail.resources.arn),
Esql.aws_cloudtrail_resources_arn_values = VALUES(aws.cloudtrail.resources.arn),
Esql.event_action_values = VALUES(event.action),
Esql.timestamp_values = VALUES(@timestamp),
Esql.aws_cloudtrail_user_identity_type_values = VALUES(aws.cloudtrail.user_identity.type),
Esql.aws_cloudtrail_user_identity_access_key_id_values = VALUES(aws.cloudtrail.user_identity.access_key_id),
Esql.cloud_account_id_values = VALUES(cloud.account.id),
Esql.cloud_region_values = VALUES(cloud.region),
Esql.user_agent_original_values = VALUES(user_agent.original),
Esql.source_as_organization_name_values = VALUES(source.as.organization.name),
Esql.data_stream_namespace_values = VALUES(data_stream.namespace)
by Esql.time_window_date_trunc, aws.cloudtrail.user_identity.arn, source.ip
| where Esql.bucket_arn_count_distinct > 15