AWS Access Token Used from Multiple Addresses

Last updated a month ago on 2025-12-04
Created 9 months ago on 2025-04-11

About

This rule identifies potentially suspicious activity by detecting instances where a single IAM user's temporary session token is accessed from multiple IP addresses within a short time frame. Such behavior may suggest that an adversary has compromised temporary credentials and is utilizing them from various locations. To enhance detection accuracy and minimize false positives, the rule incorporates criteria that evaluate unique IP addresses, user agents, cities, and networks. These additional checks help distinguish between legitimate distributed access patterns and potential credential misuse. Detected activities are classified into different types based on the combination of unique indicators, with each classification assigned a fidelity score reflecting the likelihood of malicious behavior. High fidelity scores are given to patterns most indicative of threats, such as multiple unique IPs, networks, cities, and user agents. Medium and low fidelity scores correspond to less severe patterns, enabling security teams to effectively prioritize alerts.
Tags
Domain: CloudData Source: AWSData Source: Amazon Web ServicesData Source: AWS IAMData Source: AWS CloudTrailTactic: Initial AccessUse Case: Identity and Access AuditLanguage: esql
Severity
medium
Risk Score
47
MITRE ATT&CK™

Initial Access (TA0001)(external, opens in a new tab or window)

False Positive Examples
Highly distributed environments (e.g., globally deployed automation or edge nodes) may cause a single IAM user to appear from multiple IPs. Review the geolocation, network context, and user agent patterns to rule out benign use.
License
Elastic License v2(external, opens in a new tab or window)

Definition

Integration Pack
Prebuilt Security Detection Rules
Related Integrations

aws(external, opens in a new tab or window)

Query
text code block:
from logs-aws.cloudtrail* metadata _id, _version, _index | where @timestamp > now() - 30 minutes 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 and aws.cloudtrail.user_identity.access_key_id is not null and not ( user_agent.original like "*Terraform*" or user_agent.original like "*Ansible*" or user_agent.original like "*Pulumi*" ) and `source.as.organization.name` != "AMAZON-AES" and not (( `source.as.organization.name` == "AMAZON-02" and aws.cloudtrail.event_category == "Data")) 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", "account.amazonaws.com", "budgets.amazonaws.com", "freetier.amazonaws.com", "support.amazonaws.com", "support-console.amazonaws.com" ) | eval Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), Esql.aws_cloudtrail_user_identity_arn = aws.cloudtrail.user_identity.arn, Esql.aws_cloudtrail_user_identity_access_key_id = aws.cloudtrail.user_identity.access_key_id, Esql.source_ip = source.ip, Esql.user_agent_original = user_agent.original, Esql.source_ip_string = to_string(source.ip), Esql.source_ip_user_agent_pair = concat(Esql.source_ip_string, " - ", user_agent.original), Esql.source_ip_city_pair = concat(Esql.source_ip_string, " - ", source.geo.city_name), Esql.source_geo_city_name = source.geo.city_name, Esql.source_network_org_name = `source.as.organization.name`, Esql.source_ip_network_pair = concat(Esql.source_ip_string, "-", `source.as.organization.name`), Esql.event_timestamp = @timestamp, Esql.data_stream_namespace = data_stream.namespace | stats Esql.event_action_values = values(event.action), Esql.event_provider_values = values(event.provider), Esql.aws_cloudtrail_user_identity_access_key_id_values = values(Esql.aws_cloudtrail_user_identity_access_key_id), Esql.aws_cloudtrail_user_identity_arn_values = values(Esql.aws_cloudtrail_user_identity_arn), Esql.source_ip_values = values(Esql.source_ip), Esql.user_agent_original_values = values(Esql.user_agent_original), Esql.source_ip_user_agent_pair_values = values(Esql.source_ip_user_agent_pair), Esql.source_geo_city_name_values = values(Esql.source_geo_city_name), Esql.source_ip_city_pair_values = values(Esql.source_ip_city_pair), Esql.source_network_org_name_values = values(Esql.source_network_org_name), Esql.source_ip_network_pair_values = values(Esql.source_ip_network_pair), Esql.source_ip_count_distinct = count_distinct(Esql.source_ip), Esql.user_agent_original_count_distinct = count_distinct(Esql.user_agent_original), Esql.source_geo_city_name_count_distinct = count_distinct(Esql.source_geo_city_name), Esql.source_network_org_name_count_distinct = count_distinct(Esql.source_network_org_name), Esql.data_stream_namespace_values = values(Esql.data_stream_namespace), Esql.timestamp_first_seen = min(Esql.event_timestamp), Esql.timestamp_last_seen = max(Esql.event_timestamp), Esql.event_count = count() by Esql.time_window_date_trunc, Esql.aws_cloudtrail_user_identity_access_key_id | eval Esql.activity_type = case( Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2 and Esql.user_agent_original_count_distinct >= 2, "multiple_ip_network_city_user_agent", Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_network_city", Esql.source_ip_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_and_city", Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2, "multiple_ip_and_network", Esql.source_ip_count_distinct >= 2 and Esql.user_agent_original_count_distinct >= 2, "multiple_ip_and_user_agent", "normal_activity" ), Esql.activity_fidelity_score = case( Esql.activity_type == "multiple_ip_network_city_user_agent", "high", Esql.activity_type == "multiple_ip_network_city", "high", Esql.activity_type == "multiple_ip_and_city", "medium", Esql.activity_type == "multiple_ip_and_network", "medium", Esql.activity_type == "multiple_ip_and_user_agent", "low" ) | keep Esql.time_window_date_trunc, Esql.activity_type, Esql.activity_fidelity_score, Esql.event_count, Esql.timestamp_first_seen, Esql.timestamp_last_seen, Esql.aws_cloudtrail_user_identity_arn_values, Esql.aws_cloudtrail_user_identity_access_key_id_values, Esql.event_action_values, Esql.event_provider_values, Esql.source_ip_values, Esql.user_agent_original_values, Esql.source_ip_user_agent_pair_values, Esql.source_geo_city_name_values, Esql.source_ip_city_pair_values, Esql.source_network_org_name_values, Esql.source_ip_network_pair_values, Esql.source_ip_count_distinct, Esql.user_agent_original_count_distinct, Esql.source_geo_city_name_count_distinct, Esql.source_network_org_name_count_distinct, Esql.data_stream_namespace_values | where Esql.activity_fidelity_score == "high" // this rule only alerts for "high" fidelity cases, to broaden the rule scope to include all activity // change the final condition to // | where Esql.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(external, opens in a new tab or window).