FROM logs-aws.cloudtrail* metadata _id, _version, _index
| WHERE @timestamp > NOW() - 30 minutes
// filter on CloudTrail logs for STS temporary session tokens used by IAM users
AND event.dataset == "aws.cloudtrail"
AND aws.cloudtrail.user_identity.arn IS NOT NULL
AND aws.cloudtrail.user_identity.type == "IAMUser"
AND source.ip IS NOT NULL
// exclude known benign IaC tools and Amazon Network
AND NOT (user_agent.original LIKE "%Terraform%" OR user_agent.original LIKE "%Ansible%" OR user_agent.original LIKE "%Pulumni%")
AND `source.as.organization.name` != "AMAZON-AES"
// exclude noisy service APIs less indicative of malicous behavior
AND event.provider NOT IN ("health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com")
| EVAL
// create a time window for aggregation
time_window = DATE_TRUNC(30 minutes, @timestamp),
// capture necessary fields for detection and investigation
user_id = aws.cloudtrail.user_identity.arn,
access_key_id = aws.cloudtrail.user_identity.access_key_id,
ip = source.ip,
user_agent = user_agent.original,
ip_string = TO_STRING(source.ip), // Convert IP to string
ip_user_agent_pair = CONCAT(ip_string, " - ", user_agent.original), // Combine IP and user agent
ip_city_pair = CONCAT(ip_string, " - ", source.geo.city_name), // Combine IP and city
city = source.geo.city_name,
event_time = @timestamp,
network_arn = `source.as.organization.name`
| STATS
event_actions = VALUES(event.action),
event_providers = VALUES(event.provider),
access_key_id = VALUES(access_key_id),
user_id = VALUES(user_id),
ip_list = VALUES(ip), // Collect list of IPs
user_agent_list = VALUES(user_agent), // Collect list of user agents
ip_user_agent_pairs = VALUES(ip_user_agent_pair), // Collect list of IP - user agent pairs
cities_list = VALUES(city), // Collect list of cities
ip_city_pairs = VALUES(ip_city_pair), // Collect list of IP - city pairs
networks_list = VALUES(network_arn), // Collect list of networks
unique_ips = COUNT_DISTINCT(ip),
unique_user_agents = COUNT_DISTINCT(user_agent),
unique_cities = COUNT_DISTINCT(city),
unique_networks = COUNT_DISTINCT(network_arn),
first_seen = MIN(event_time),
last_seen = MAX(event_time),
total_events = COUNT()
BY time_window, access_key_id
| EVAL
// activity type based on combinations of detection criteria
activity_type = CASE(
unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2 AND unique_user_agents >= 2, "multiple_ip_network_city_user_agent", // high severity
unique_ips >= 2 AND unique_networks >= 2 AND unique_cities >= 2, "multiple_ip_network_city", // high severity
unique_ips >= 2 AND unique_cities >= 2, "multiple_ip_and_city", // medium severity
unique_ips >= 2 AND unique_networks >= 2, "multiple_ip_and_network", // medium severity
unique_ips >= 2 AND unique_user_agents >= 2, "multiple_ip_and_user_agent", // low severity
"normal_activity"
),
// likelihood of malicious activity based on activity type
fidelity_score = CASE(
activity_type == "multiple_ip_network_city_user_agent", "high",
activity_type == "multiple_ip_network_city", "high",
activity_type == "multiple_ip_and_city", "medium",
activity_type == "multiple_ip_and_network", "medium",
activity_type == "multiple_ip_and_user_agent", "low"
)
| KEEP
time_window, activity_type, fidelity_score, total_events, first_seen, last_seen,
user_id, access_key_id, event_actions, event_providers, ip_list, user_agent_list, ip_user_agent_pairs, cities_list, ip_city_pairs, networks_list, unique_ips, unique_user_agents, unique_cities, unique_networks
| WHERE activity_type != "normal_activity"
Install detection rules in Elastic Security
Detect AWS Access Token Used from Multiple Addresses in the Elastic Security detection engine by installing this rule into your Elastic Stack.
To setup this rule, check out the installation guide for Prebuilt Security Detection Rules(opens in a new tab or window).