From 3bfbafe583115cd70b50df94fc09b7b4d99a41b3 Mon Sep 17 00:00:00 2001 From: Isai <59296946+imays11@users.noreply.github.com> Date: Fri, 5 Dec 2025 11:48:22 -0500 Subject: [PATCH] [Rule Tuning] AWS Access Token Used from Multiple Addresses (#5412) * [Rule Tuning] AWS Access Token Used from Multiple Addresses This rule is extremely loud in telemetry ~2612 alerts in last 24 hours. There have also been a couple community requests for changes. - reduced the scope of the alerts to only surface the "high" fidelity_score cases for `"multiple_ip_network_city"` or `"multiple_ip_network_city_user_agent"` criteria. This reduced telemetry by ~90% - excluded 2 more benign service providers `support` which reduced volume by another 6%. - added the `data_stream.namespace` field as requested. - kept the rest of the rule logic visible so that if customers would like to broaden the scope of this rule again, they can duplicate the rules and revert back to the broader condition `Esql.activity_type != "normal_activity"`. This has been included as a comment in the rule query. I will keep an eye on this rule in telemetry to determine it's value moving forward. * nit IG format changes --- ...on_token_used_from_multiple_addresses.toml | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml index a076f622d..6e81c88cc 100644 --- a/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml +++ b/rules/integrations/aws/initial_access_iam_session_token_used_from_multiple_addresses.toml @@ -2,7 +2,7 @@ creation_date = "2025/04/11" integration = ["aws"] maturity = "production" -updated_date = "2025/09/02" +updated_date = "2025/12/04" [rule] author = ["Elastic"] @@ -35,7 +35,7 @@ note = """## Triage and Analysis Access tokens are bound to a single user. Usage from multiple IP addresses may indicate the token was stolen and used elsewhere. By correlating this with additional detection criteria like multiple user agents, different cities, and different networks, we can improve the fidelity of the rule and help to eliminate false positives associated with expected behavior, like dual-stack IPV4/IPV6 usage. -#### Possible Investigation Steps +#### Possible investigation steps - **Identify the IAM User**: Examine the `aws.cloudtrail.user_identity.arn` stored in `user_id` and correlate with the `source.ips` stored in `ip_list` and `unique_ips` count to determine how widely the token was used. - **Correlate Additional Detection Context**: Examine `activity_type` and `fidelity_score` to determine additional cities, networks or user agents associated with the token usage. @@ -44,18 +44,18 @@ Access tokens are bound to a single user. Usage from multiple IP addresses may i - **Review Workload Context**: Confirm whether the user was expected to be active across multiple cities, networks or user agent environments. - **Trace Adversary Movement**: Pivot to related actions (e.g., `s3:ListBuckets`, `iam:ListUsers`, `sts:GetCallerIdentity`) to track further enumeration. -### False Positive Analysis +### False positive analysis - Automation frameworks that rotate through multiple IPs or cloud functions with dynamic egress IPs may cause this alert to fire. - Confirm geolocation and workload context before escalating. -### Response and Remediation +### Response and remediation - **Revoke the Token**: Disable or rotate the IAM credentials and invalidate the temporary session token. - **Audit the Environment**: Look for signs of lateral movement or data access during the token's validity. - **Strengthen Controls**: Require MFA for high-privilege actions, restrict access via policy conditions (e.g., IP range or device). -### References +### Additional information - [IAM Long-Term Credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) - [STS Temporary Credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html) @@ -99,7 +99,8 @@ from logs-aws.cloudtrail* metadata _id, _version, _index "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" + "account.amazonaws.com", "budgets.amazonaws.com", "freetier.amazonaws.com", "support.amazonaws.com", + "support-console.amazonaws.com" ) | eval @@ -114,7 +115,8 @@ from logs-aws.cloudtrail* metadata _id, _version, _index 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.event_timestamp = @timestamp, + Esql.data_stream_namespace = data_stream.namespace | stats Esql.event_action_values = values(event.action), @@ -132,6 +134,7 @@ from logs-aws.cloudtrail* metadata _id, _version, _index 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() @@ -175,9 +178,15 @@ from logs-aws.cloudtrail* metadata _id, _version, _index 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.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" -| where Esql.activity_type != "normal_activity" ''' [rule.investigation_fields] @@ -201,7 +210,8 @@ field_names = [ "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.source_network_org_name_count_distinct", + "Esql.data_stream_namespace_values" ]