|
| 1 | +[metadata] |
| 2 | +creation_date = "2026/04/21" |
| 3 | +integration = ["aws"] |
| 4 | +maturity = "production" |
| 5 | +updated_date = "2026/04/21" |
| 6 | + |
| 7 | +[rule] |
| 8 | +author = ["Elastic"] |
| 9 | +description = """ |
| 10 | +Surfaces an AWS identity whose successful API traffic is dominated by a small set of large cloud-provider source AS |
| 11 | +organization labels, yet also shows a very small share of traffic from other AS organization names—including at least one |
| 12 | +sensitive control-plane, credential, storage, or model-invocation action on that uncommon network path with recent |
| 13 | +activity from the uncommon path. The intent is to highlight disproportionate “baseline” cloud egress versus sparse use |
| 14 | +from rarer networks on the same principal, a shape that can appear when automation or CI credentials are reused or |
| 15 | +pivoted outside their usual hosted-cloud footprint. |
| 16 | +""" |
| 17 | +false_positives = [ |
| 18 | + """ |
| 19 | + Global employees on VPNs, split DNS or proxy paths that change AS labels, regional carrier rebrands, or mobile |
| 20 | + hotspots can produce a small non-cloud AS share on the same IAM user as hyperscaler- or SaaS-classified traffic. |
| 21 | + Corporate travel, emergency break-glass from a home ISP, and multi-region runners may also widen AS diversity without |
| 22 | + malice. Tune thresholds, add account or principal allowlists, or narrow the sensitive-action list after baseline review. |
| 23 | + """, |
| 24 | +] |
| 25 | +from = "now-7d" |
| 26 | +interval = "1h" |
| 27 | +language = "esql" |
| 28 | +license = "Elastic License v2" |
| 29 | +name = "AWS Rare Source AS Organization Activity" |
| 30 | +note = """## Triage and analysis |
| 31 | +
|
| 32 | +### Investigating AWS Rare Source AS Organization Activity After High Cloud-Provider Volume |
| 33 | +
|
| 34 | +The rule aggregates roughly seven days of successful CloudTrail per `user.name` and `aws.cloudtrail.user_identity.type`. |
| 35 | +It expects a **high count** of events whose GeoIP AS organization matches a short allowlist of large cloud/SaaS providers, |
| 36 | +**at least one** event from a different AS organization, a **low ratio** of uncommon-network events to all events, few |
| 37 | +**distinct** uncommon AS labels, and **recent** uncommon-network timestamps. It further requires at least one |
| 38 | +**sensitive** API from an uncommon network (see query `event.action` list). |
| 39 | +
|
| 40 | +#### Possible investigation steps |
| 41 | +
|
| 42 | +- Compare `Esql.src_asn_values` to `Esql.user_agent_values` and map each `source.ip` (from raw CloudTrail) to expected |
| 43 | + admin paths, pipelines, or offices. |
| 44 | +- Pivot on `user.name` and `aws.cloudtrail.user_identity.access_key_id` (from underlying events) for IAM, STS, S3, and |
| 45 | + Secrets Manager activity around `Esql.most_recent_low_asn_day`. |
| 46 | +- Confirm whether the identity is meant for automation only; if so, rare human ISP ASNs warrant higher scrutiny. |
| 47 | +- Review `Esql.untrusted_suspicious_actions` for the mix of discovery versus privilege-changing APIs. |
| 48 | +
|
| 49 | +### False positive analysis |
| 50 | +
|
| 51 | +- **Threshold sensitivity**: Raise `Esql.trusted_cloud_event_count` or Lower `Esql.rare_asn_ratio` and `Esql.untrusted_event_count` if legitimate rare-ASN |
| 52 | + noise persists. |
| 53 | +- **MongoDB / other allowlist labels**: Extend `is_trusted_cloud` if your approved automation consistently appears under |
| 54 | + another legal-entity string. |
| 55 | +
|
| 56 | +### Response and remediation |
| 57 | +
|
| 58 | +- If abuse is plausible: rotate credentials for the principal, enforce OIDC or short-lived keys for automation, and |
| 59 | + tighten IAM and data-plane permissions. |
| 60 | +
|
| 61 | +### Additional information |
| 62 | +
|
| 63 | +- [CloudTrail user identity](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html) |
| 64 | +""" |
| 65 | +references = [ |
| 66 | + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference.html", |
| 67 | +] |
| 68 | +risk_score = 73 |
| 69 | +rule_id = "d1b37c0b-4f8b-4cfb-9a1d-639bf8c028b7" |
| 70 | +severity = "high" |
| 71 | +tags = [ |
| 72 | + "Domain: Cloud", |
| 73 | + "Data Source: AWS", |
| 74 | + "Data Source: Amazon Web Services", |
| 75 | + "Data Source: AWS CloudTrail", |
| 76 | + "Use Case: Threat Detection", |
| 77 | + "Tactic: Initial Access", |
| 78 | + "Resources: Investigation Guide", |
| 79 | +] |
| 80 | +timestamp_override = "event.ingested" |
| 81 | +type = "esql" |
| 82 | + |
| 83 | +query = ''' |
| 84 | +FROM logs-aws.cloudtrail-* |
| 85 | +| WHERE event.dataset == "aws.cloudtrail" |
| 86 | + AND event.outcome == "success" |
| 87 | + AND source.as.organization.name IS NOT NULL |
| 88 | + AND user.name IS NOT NULL |
| 89 | +
|
| 90 | +| EVAL is_trusted_cloud = CASE( |
| 91 | + source.as.organization.name LIKE "Amazon*" OR |
| 92 | + source.as.organization.name == "Google LLC" OR |
| 93 | + source.as.organization.name == "Microsoft Corporation" OR |
| 94 | + source.as.organization.name == "MongoDB, Inc.", |
| 95 | + true, false |
| 96 | + ) |
| 97 | +
|
| 98 | +| EVAL is_suspicious_action = CASE( |
| 99 | + event.action IN ( |
| 100 | + "GetCallerIdentity", "GetAccountSummary", "ListAccountAliases", |
| 101 | + "GetSecretValue", "ListSecrets", "DescribeSecret", |
| 102 | + "GetParameter", "GetParameters", "GetParametersByPath", |
| 103 | + "AssumeRole", "AssumeRoleWithWebIdentity", "AssumeRoleWithSAML", |
| 104 | + "AttachUserPolicy", "AttachRolePolicy", |
| 105 | + "PutUserPolicy", "PutRolePolicy", |
| 106 | + "CreateAccessKey", "UpdateAccessKey", |
| 107 | + "CreateUser", "CreateLoginProfile", |
| 108 | + "UpdateLoginProfile", "AddUserToGroup", |
| 109 | + "GetObject", "ListBuckets", "ListObjects", "ListObjectsV2", |
| 110 | + "InvokeModel", "InvokeModelWithResponseStream", "Converse" |
| 111 | + ), true, false |
| 112 | + ) |
| 113 | +
|
| 114 | +// Single aggregation — full event count preserved for ratio logic |
| 115 | +// suspicious action tracking is additive on top |
| 116 | +| STATS |
| 117 | + Esql.total_events_all_asns = COUNT(*), |
| 118 | + Esql.count_distinct_asns = COUNT_DISTINCT(source.as.organization.name), |
| 119 | + Esql.src_asn_values = VALUES(source.as.organization.name), |
| 120 | + Esql.user_agent_values = VALUES(user_agent.original), |
| 121 | + Esql.related_users = VALUES(user.changes.name), |
| 122 | + Esql.source_ip_values = VALUES(source.address), |
| 123 | + Esql.has_trusted_cloud_asn = MAX(is_trusted_cloud), |
| 124 | + Esql.trusted_cloud_event_count = SUM(CASE(is_trusted_cloud == true, 1, 0)), |
| 125 | + Esql.untrusted_event_count = SUM(CASE(is_trusted_cloud == false, 1, 0)), |
| 126 | + // Suspicious action visibility from untrusted ASNs — informational only, not a filter |
| 127 | + Esql.untrusted_suspicious_count = SUM(CASE( |
| 128 | + is_trusted_cloud == false AND is_suspicious_action == true, 1, 0 |
| 129 | + )), |
| 130 | + Esql.untrusted_suspicious_actions = VALUES(CASE( |
| 131 | + is_trusted_cloud == false AND is_suspicious_action == true, |
| 132 | + event.action, null |
| 133 | + )), |
| 134 | + Esql.most_recent_low_asn_day = MAX(CASE( |
| 135 | + is_trusted_cloud == false, @timestamp, null |
| 136 | + )) |
| 137 | + BY user.name, aws.cloudtrail.user_identity.type |
| 138 | +
|
| 139 | +| EVAL Esql.rare_asn_ratio = TO_DOUBLE(Esql.untrusted_event_count) / TO_DOUBLE(Esql.total_events_all_asns), |
| 140 | + Esql.unique_action_from_untrusted_asn = MV_COUNT(Esql.untrusted_suspicious_actions) |
| 141 | +
|
| 142 | +// Detection thresholds — unchanged, full event counts drive the logic |
| 143 | +| WHERE Esql.has_trusted_cloud_asn == true |
| 144 | + AND Esql.untrusted_event_count >= 1 |
| 145 | + AND Esql.trusted_cloud_event_count >= 100 |
| 146 | + AND Esql.rare_asn_ratio <= 0.01 |
| 147 | + AND Esql.unique_action_from_untrusted_asn >= 2 |
| 148 | + AND Esql.count_distinct_asns <= 5 |
| 149 | + AND Esql.most_recent_low_asn_day >= NOW() - 1 hour |
| 150 | +
|
| 151 | +| KEEP user.name, |
| 152 | + aws.cloudtrail.user_identity.type, |
| 153 | + Esql.* |
| 154 | +''' |
| 155 | + |
| 156 | +[rule.investigation_fields] |
| 157 | +field_names = [ |
| 158 | + "user.name", |
| 159 | + "aws.cloudtrail.user_identity.type", |
| 160 | + "Esql.*" |
| 161 | +] |
| 162 | + |
| 163 | + |
| 164 | + |
| 165 | +[[rule.threat]] |
| 166 | +framework = "MITRE ATT&CK" |
| 167 | + |
| 168 | +[[rule.threat.technique]] |
| 169 | +id = "T1078" |
| 170 | +name = "Valid Accounts" |
| 171 | +reference = "https://attack.mitre.org/techniques/T1078/" |
| 172 | + |
| 173 | +[[rule.threat.technique.subtechnique]] |
| 174 | +id = "T1078.004" |
| 175 | +name = "Cloud Accounts" |
| 176 | +reference = "https://attack.mitre.org/techniques/T1078/004/" |
| 177 | + |
| 178 | +[rule.threat.tactic] |
| 179 | +id = "TA0001" |
| 180 | +name = "Initial Access" |
| 181 | +reference = "https://attack.mitre.org/tactics/TA0001/" |
0 commit comments