|
| 1 | +[[prebuilt-rule-8-19-4-aws-access-token-used-from-multiple-addresses]] |
| 2 | +=== AWS Access Token Used from Multiple Addresses |
| 3 | + |
| 4 | +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. |
| 5 | + |
| 6 | +*Rule type*: esql |
| 7 | + |
| 8 | +*Rule indices*: None |
| 9 | + |
| 10 | +*Severity*: medium |
| 11 | + |
| 12 | +*Risk score*: 47 |
| 13 | + |
| 14 | +*Runs every*: 5m |
| 15 | + |
| 16 | +*Searches indices from*: now-32m ({ref}/common-options.html#date-math[Date Math format], see also <<rule-schedule, `Additional look-back time`>>) |
| 17 | + |
| 18 | +*Maximum alerts per execution*: 100 |
| 19 | + |
| 20 | +*References*: |
| 21 | + |
| 22 | +* https://www.sygnia.co/blog/sygnia-investigation-bybit-hack/ |
| 23 | + |
| 24 | +*Tags*: |
| 25 | + |
| 26 | +* Domain: Cloud |
| 27 | +* Data Source: AWS |
| 28 | +* Data Source: Amazon Web Services |
| 29 | +* Data Source: AWS IAM |
| 30 | +* Data Source: AWS CloudTrail |
| 31 | +* Tactic: Initial Access |
| 32 | +* Use Case: Identity and Access Audit |
| 33 | +* Resources: Investigation Guide |
| 34 | + |
| 35 | +*Version*: 102 |
| 36 | + |
| 37 | +*Rule authors*: |
| 38 | + |
| 39 | +* Elastic |
| 40 | + |
| 41 | +*Rule license*: Elastic License v2 |
| 42 | + |
| 43 | + |
| 44 | +==== Investigation guide |
| 45 | + |
| 46 | + |
| 47 | + |
| 48 | +*Triage and Analysis* |
| 49 | + |
| 50 | + |
| 51 | + |
| 52 | +*Investigating AWS Access Token Used from Multiple Addresses* |
| 53 | + |
| 54 | + |
| 55 | +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. |
| 56 | + |
| 57 | + |
| 58 | +*Possible Investigation Steps* |
| 59 | + |
| 60 | + |
| 61 | +- **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. |
| 62 | +- **Correlate Additional Detection Context**: Examine `activity_type` and `fidelity_score` to determine additional cities, networks or user agents associated with the token usage. |
| 63 | +- **Determine Access Key Type**: Examine the `access_key_id` to determine whether the token is short-term (beginning with ASIA) or long-term (beginning with AKIA). |
| 64 | +- **Check Recent MFA Events**: Determine whether the user recently enabled MFA, registered devices, or assumed a role using this token. |
| 65 | +- **Review Workload Context**: Confirm whether the user was expected to be active across multiple cities, networks or user agent environments. |
| 66 | +- **Trace Adversary Movement**: Pivot to related actions (e.g., `s3:ListBuckets`, `iam:ListUsers`, `sts:GetCallerIdentity`) to track further enumeration. |
| 67 | + |
| 68 | + |
| 69 | +*False Positive Analysis* |
| 70 | + |
| 71 | + |
| 72 | +- Automation frameworks that rotate through multiple IPs or cloud functions with dynamic egress IPs may cause this alert to fire. |
| 73 | +- Confirm geolocation and workload context before escalating. |
| 74 | + |
| 75 | + |
| 76 | +*Response and Remediation* |
| 77 | + |
| 78 | + |
| 79 | +- **Revoke the Token**: Disable or rotate the IAM credentials and invalidate the temporary session token. |
| 80 | +- **Audit the Environment**: Look for signs of lateral movement or data access during the token's validity. |
| 81 | +- **Strengthen Controls**: Require MFA for high-privilege actions, restrict access via policy conditions (e.g., IP range or device). |
| 82 | + |
| 83 | + |
| 84 | +*References* |
| 85 | + |
| 86 | + |
| 87 | +- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html[IAM Long-Term Credentials] |
| 88 | +- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html[STS Temporary Credentials] |
| 89 | +- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_enable-regions.html[Using MFA with Temporary Credentials] |
| 90 | +- https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html[AWS Threat Detection Use Cases] |
| 91 | + |
| 92 | + |
| 93 | +==== Rule query |
| 94 | + |
| 95 | + |
| 96 | +[source, js] |
| 97 | +---------------------------------- |
| 98 | +from logs-aws.cloudtrail* metadata _id, _version, _index |
| 99 | +| where @timestamp > now() - 30 minutes |
| 100 | + and event.dataset == "aws.cloudtrail" |
| 101 | + and aws.cloudtrail.user_identity.arn is not null |
| 102 | + and aws.cloudtrail.user_identity.type == "IAMUser" |
| 103 | + and source.ip is not null |
| 104 | + and not ( |
| 105 | + user_agent.original like "%Terraform%" or |
| 106 | + user_agent.original like "%Ansible%" or |
| 107 | + user_agent.original like "%Pulumni%" |
| 108 | + ) |
| 109 | + and `source.as.organization.name` != "AMAZON-AES" |
| 110 | + and event.provider not in ( |
| 111 | + "health.amazonaws.com", "monitoring.amazonaws.com", "notifications.amazonaws.com", |
| 112 | + "ce.amazonaws.com", "cost-optimization-hub.amazonaws.com", |
| 113 | + "servicecatalog-appregistry.amazonaws.com", "securityhub.amazonaws.com" |
| 114 | + ) |
| 115 | +
|
| 116 | +| eval |
| 117 | + Esql.time_window_date_trunc = date_trunc(30 minutes, @timestamp), |
| 118 | + Esql.aws_cloudtrail_user_identity_arn = aws.cloudtrail.user_identity.arn, |
| 119 | + Esql.aws_cloudtrail_user_identity_access_key_id = aws.cloudtrail.user_identity.access_key_id, |
| 120 | + Esql.source_ip = source.ip, |
| 121 | + Esql.user_agent_original = user_agent.original, |
| 122 | + Esql.source_ip_string = to_string(source.ip), |
| 123 | + Esql.source_ip_user_agent_pair = concat(Esql.source_ip_string, " - ", user_agent.original), |
| 124 | + Esql.source_ip_city_pair = concat(Esql.source_ip_string, " - ", source.geo.city_name), |
| 125 | + Esql.source_geo_city_name = source.geo.city_name, |
| 126 | + Esql.event_timestamp = @timestamp, |
| 127 | + Esql.source_network_org_name = `source.as.organization.name` |
| 128 | +
|
| 129 | +| stats |
| 130 | + Esql.event_action_values = values(event.action), |
| 131 | + Esql.event_provider_values = values(event.provider), |
| 132 | + Esql.aws_cloudtrail_user_identity_access_key_id_values = values(Esql.aws_cloudtrail_user_identity_access_key_id), |
| 133 | + Esql.aws_cloudtrail_user_identity_arn_values = values(Esql.aws_cloudtrail_user_identity_arn), |
| 134 | + Esql.source_ip_values = values(Esql.source_ip), |
| 135 | + Esql.user_agent_original_values = values(Esql.user_agent_original), |
| 136 | + Esql.source_ip_user_agent_pair_values = values(Esql.source_ip_user_agent_pair), |
| 137 | + Esql.source_geo_city_name_values = values(Esql.source_geo_city_name), |
| 138 | + Esql.source_ip_city_pair_values = values(Esql.source_ip_city_pair), |
| 139 | + Esql.source_network_org_name_values = values(Esql.source_network_org_name), |
| 140 | + Esql.source_ip_count_distinct = count_distinct(Esql.source_ip), |
| 141 | + Esql.user_agent_original_count_distinct = count_distinct(Esql.user_agent_original), |
| 142 | + Esql.source_geo_city_name_count_distinct = count_distinct(Esql.source_geo_city_name), |
| 143 | + Esql.source_network_org_name_count_distinct = count_distinct(Esql.source_network_org_name), |
| 144 | + Esql.timestamp_first_seen = min(Esql.event_timestamp), |
| 145 | + Esql.timestamp_last_seen = max(Esql.event_timestamp), |
| 146 | + Esql.event_count = count() |
| 147 | + by Esql.time_window_date_trunc, Esql.aws_cloudtrail_user_identity_access_key_id |
| 148 | +
|
| 149 | +| eval |
| 150 | + Esql.activity_type = case( |
| 151 | + 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", |
| 152 | + 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", |
| 153 | + Esql.source_ip_count_distinct >= 2 and Esql.source_geo_city_name_count_distinct >= 2, "multiple_ip_and_city", |
| 154 | + Esql.source_ip_count_distinct >= 2 and Esql.source_network_org_name_count_distinct >= 2, "multiple_ip_and_network", |
| 155 | + Esql.source_ip_count_distinct >= 2 and Esql.user_agent_original_count_distinct >= 2, "multiple_ip_and_user_agent", |
| 156 | + "normal_activity" |
| 157 | + ), |
| 158 | + Esql.activity_fidelity_score = case( |
| 159 | + Esql.activity_type == "multiple_ip_network_city_user_agent", "high", |
| 160 | + Esql.activity_type == "multiple_ip_network_city", "high", |
| 161 | + Esql.activity_type == "multiple_ip_and_city", "medium", |
| 162 | + Esql.activity_type == "multiple_ip_and_network", "medium", |
| 163 | + Esql.activity_type == "multiple_ip_and_user_agent", "low" |
| 164 | + ) |
| 165 | +
|
| 166 | +| keep |
| 167 | + Esql.time_window_date_trunc, |
| 168 | + Esql.activity_type, |
| 169 | + Esql.activity_fidelity_score, |
| 170 | + Esql.event_count, |
| 171 | + Esql.timestamp_first_seen, |
| 172 | + Esql.timestamp_last_seen, |
| 173 | + Esql.aws_cloudtrail_user_identity_arn_values, |
| 174 | + Esql.aws_cloudtrail_user_identity_access_key_id_values, |
| 175 | + Esql.event_action_values, |
| 176 | + Esql.event_provider_values, |
| 177 | + Esql.source_ip_values, |
| 178 | + Esql.user_agent_original_values, |
| 179 | + Esql.source_ip_user_agent_pair_values, |
| 180 | + Esql.source_geo_city_name_values, |
| 181 | + Esql.source_ip_city_pair_values, |
| 182 | + Esql.source_network_org_name_values, |
| 183 | + Esql.source_ip_count_distinct, |
| 184 | + Esql.user_agent_original_count_distinct, |
| 185 | + Esql.source_geo_city_name_count_distinct, |
| 186 | + Esql.source_network_org_name_count_distinct |
| 187 | +
|
| 188 | +| where Esql.activity_type != "normal_activity" |
| 189 | +
|
| 190 | +---------------------------------- |
| 191 | + |
| 192 | +*Framework*: MITRE ATT&CK^TM^ |
| 193 | + |
| 194 | +* Tactic: |
| 195 | +** Name: Initial Access |
| 196 | +** ID: TA0001 |
| 197 | +** Reference URL: https://attack.mitre.org/tactics/TA0001/ |
| 198 | +* Technique: |
| 199 | +** Name: Valid Accounts |
| 200 | +** ID: T1078 |
| 201 | +** Reference URL: https://attack.mitre.org/techniques/T1078/ |
| 202 | +* Sub-technique: |
| 203 | +** Name: Cloud Accounts |
| 204 | +** ID: T1078.004 |
| 205 | +** Reference URL: https://attack.mitre.org/techniques/T1078/004/ |
0 commit comments