[New Rules] AWS IAM Long-Term Creds Abuse Coverage (#5918)
* [New Rules] AWS Long-Term Creds Abuse Coverage This adds a two-layer approach to long-term IAM access key (AKIA*) abuse, aligned with reporting on stolen or leaked keys often abused as seen in Kudelski Security — Trivy supply-chain report. ### Layer 1 — AWS Long-Term Access Key First Seen from Source IP (9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f) New Terms on CloudTrail when a given AKIA succeeds from a new `source.ip` in the history window. Goal: catch novel use of a durable key (travel, new egress, or attacker infrastructure). ### Layer 2 — AWS Long-Term Access Key Correlated with Elevated Detection Alerts Higher-order rule on open alerts that requires both the Layer 1 rule and at least one other open alert on the same `source.ip` at medium+ severity (or equivalent risk score). Goal: raise priority when “new IP for this key” happens together with stronger, post-compromise-style signals. The higher-order rule correlates on `source.ip` in .alerts-security.* index. In testing, I chose to tie the same sessions together using `source.ip` vs `access_key.id` because the alerts index did not expose this field for queries. Screenshots below show testing that verified the approach. The same operator/session across Layer 1 rule, the sibling alert, and the Layer 2 correlation rule for two separate lab scenarios (e.g. a high-severity sibling rule and a medium-severity sibling rule). * adding IAM to rule names * removing unnecessary ref * Fixed Mitre tactics and tags --------- Co-authored-by: Terrance DeJesus <99630311+terrancedejesus@users.noreply.github.com>
This commit is contained in:
+179
@@ -0,0 +1,179 @@
|
||||
[metadata]
|
||||
creation_date = "2026/04/03"
|
||||
integration = ["aws"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/04/03"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Correlates open detection alerts that share the same long-term IAM access key ID ( prefix AKIA). It fires when the rule
|
||||
AWS Long-Term Access Key First Seen from Source IP (rule_id: 9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f) has triggered for
|
||||
that key and at least one other open alert for the same key is medium, high, or critical severity. This higher-order
|
||||
rule helps prioritize long-term key novelty when it co-occurs with elevated detections that may indicate post-compromise
|
||||
activity.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
The same automation identity may legitimately trigger a first-seen-IP alert and unrelated medium-or-higher findings
|
||||
in the same window (for example, a noisy compliance rule). Review sibling `kibana.alert.rule.name` values, rule
|
||||
tags, and CloudTrail context for the access key before escalating.
|
||||
""",
|
||||
]
|
||||
from = "now-6m"
|
||||
language = "esql"
|
||||
license = "Elastic License v2"
|
||||
name = "AWS IAM Long-Term Access Key Correlated with Elevated Detection Alerts"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating AWS IAM Long-Term Access Key Correlated with Elevated Detection Alerts
|
||||
|
||||
This is a [higher-order](https://www.elastic.co/guide/en/security/current/rules-ui-create.html) rule. It evaluates **open** signals in `.alerts-security.*` grouped by
|
||||
`aws.cloudtrail.user_identity.access_key_id`. A match requires:
|
||||
|
||||
1. At least one alert from **AWS Long-Term Access Key First Seen from Source IP** (`kibana.alert.rule.rule_id` **9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f**).
|
||||
2. At least one **different** alert on the **same access key** with **medium or higher** severity, implemented as
|
||||
`kibana.alert.risk_score >= 47` or `kibana.alert.severity` in `medium`, `high`, or `critical`.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
- **`Esql.alert_count_long_term_key_new_ip_rule`**: Confirm the first-seen-IP rule contributed to the correlation.
|
||||
- **`Esql.alert_count_other_elevated`**: Count of sibling alerts meeting the elevated threshold (excluding the long-term-key new-IP rule).
|
||||
- **`Esql.kibana_alert_rule_name_values`** / **`Esql.kibana_alert_rule_id_values`**: Identify which rules fired; map tactics and whether activity looks like post-compromise follow-through.
|
||||
- **`aws.cloudtrail.user_identity.access_key_id`**: Pivot in CloudTrail and IAM (last used, attached policies, user or root context).
|
||||
- **`Esql.aws_cloudtrail_user_identity_arn_values`**, **`Esql.source_ip_values`**: Reconstruct session context across alerts.
|
||||
|
||||
### False positive analysis
|
||||
|
||||
- Overlapping scheduled jobs, penetration tests, or purple-team runs that reuse the same IAM access key across many detectors.
|
||||
- Custom rules calibrated with **medium** severity but **low** risk scores still qualify via `kibana.alert.severity`.
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- Treat as elevated priority versus the standalone first-seen-IP rule: rotate or disable the access key if abuse is suspected,
|
||||
scope CloudTrail for the key, and review open sibling alerts to closure.
|
||||
|
||||
### Additional information
|
||||
|
||||
- Default risk score to severity mapping in Elastic Security is commonly **21** low, **47** medium, **73** high, **99** critical; confirm in your deployment if customized.
|
||||
|
||||
"""
|
||||
references = [
|
||||
"https://kudelskisecurity.com/research/investigating-two-variants-of-the-trivy-supply-chain-compromise",
|
||||
]
|
||||
risk_score = 73
|
||||
rule_id = "98cfaa44-83f0-4aba-90c4-363fb9d51a75"
|
||||
severity = "high"
|
||||
tags = [
|
||||
"Domain: Cloud",
|
||||
"Data Source: AWS",
|
||||
"Data Source: Amazon Web Services",
|
||||
"Data Source: AWS CloudTrail",
|
||||
"Data Source: AWS IAM",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Credential Access",
|
||||
"Tactic: Initial Access",
|
||||
"Resources: Investigation Guide",
|
||||
"Rule Type: Higher-Order Rule",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "esql"
|
||||
|
||||
query = '''
|
||||
from .alerts-security.* METADATA _id, _version, _index
|
||||
|
||||
// Sibling rule: AWS Long-Term Access Key First Seen from Source IP
|
||||
// rule_id = 9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f
|
||||
|
||||
| where kibana.alert.workflow_status == "open"
|
||||
and event.kind == "signal"
|
||||
and source.ip is not null
|
||||
and kibana.alert.rule.name is not null
|
||||
and not kibana.alert.rule.type in ("threat_match", "machine_learning")
|
||||
and not kibana.alert.rule.name like "Deprecated - *"
|
||||
and not KQL("""kibana.alert.rule.tags : "Rule Type: Higher-Order Rule" """)
|
||||
and (
|
||||
kibana.alert.rule.rule_id == "9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f"
|
||||
or kibana.alert.risk_score >= 47
|
||||
or kibana.alert.severity in ("medium", "high", "critical")
|
||||
)
|
||||
|
||||
| eval Esql.is_long_term_key_new_ip_rule = kibana.alert.rule.rule_id == "9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f"
|
||||
| eval Esql.is_other_elevated_rule = kibana.alert.rule.rule_id != "9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f"
|
||||
and (
|
||||
kibana.alert.risk_score >= 47
|
||||
or kibana.alert.severity in ("medium", "high", "critical")
|
||||
)
|
||||
|
||||
| stats
|
||||
Esql.alert_count_long_term_key_new_ip_rule = SUM(CASE(Esql.is_long_term_key_new_ip_rule, 1, 0)),
|
||||
Esql.alert_count_other_elevated_rule = SUM(CASE(Esql.is_other_elevated_rule, 1, 0)),
|
||||
Esql.kibana_alert_rule_name_values = VALUES(kibana.alert.rule.name),
|
||||
Esql.kibana_alert_rule_id_values = VALUES(kibana.alert.rule.rule_id),
|
||||
Esql.kibana_alert_risk_score_values = VALUES(kibana.alert.risk_score),
|
||||
Esql.kibana_alert_severity_values = VALUES(kibana.alert.severity),
|
||||
Esql.user_entity_id_values = VALUES(user.entity.id),
|
||||
Esql.timestamp_min = MIN(@timestamp),
|
||||
Esql.timestamp_max = MAX(@timestamp)
|
||||
by source.ip
|
||||
|
||||
| where Esql.alert_count_long_term_key_new_ip_rule > 0
|
||||
and Esql.alert_count_other_elevated_rule > 0
|
||||
|
||||
| keep
|
||||
source.ip,
|
||||
Esql.alert_count_long_term_key_new_ip_rule,
|
||||
Esql.alert_count_other_elevated_rule,
|
||||
Esql.kibana_alert_rule_name_values,
|
||||
Esql.kibana_alert_rule_id_values,
|
||||
Esql.kibana_alert_risk_score_values,
|
||||
Esql.kibana_alert_severity_values,
|
||||
Esql.user_entity_id_values,
|
||||
Esql.timestamp_min,
|
||||
Esql.timestamp_max
|
||||
'''
|
||||
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1552"
|
||||
name = "Unsecured Credentials"
|
||||
reference = "https://attack.mitre.org/techniques/T1552/"
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0006"
|
||||
name = "Credential Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0006/"
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1078"
|
||||
name = "Valid Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/"
|
||||
[[rule.threat.technique.subtechnique]]
|
||||
id = "T1078.004"
|
||||
name = "Cloud Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/004/"
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0001"
|
||||
name = "Initial Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0001/"
|
||||
|
||||
[rule.investigation_fields]
|
||||
field_names = [
|
||||
"source.ip",
|
||||
"Esql.user_entity_id_values",
|
||||
"Esql.alert_count_long_term_key_new_ip_rule",
|
||||
"Esql.alert_count_other_elevated_rule",
|
||||
"Esql.kibana_alert_rule_name_values",
|
||||
"Esql.kibana_alert_rule_id_values",
|
||||
"Esql.kibana_alert_risk_score_values",
|
||||
"Esql.kibana_alert_severity_values",
|
||||
"Esql.timestamp_min",
|
||||
"Esql.timestamp_max",
|
||||
]
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
[metadata]
|
||||
creation_date = "2026/04/03"
|
||||
integration = ["aws"]
|
||||
maturity = "production"
|
||||
updated_date = "2026/04/03"
|
||||
|
||||
[rule]
|
||||
author = ["Elastic"]
|
||||
description = """
|
||||
Identifies the first time, within the configured history window, that a long-term IAM access key ID (prefix AKIA) is
|
||||
used successfully from a given source.ip in AWS CloudTrail. Long-term access keys belong to IAM users or the account
|
||||
root user. They are a common target after credential theft or leakage, including supply-chain and exposed-key scenarios.
|
||||
Temporary security credentials (prefix ASIA) and console sessions are excluded so the signal emphasizes programmatic
|
||||
access patterns.
|
||||
"""
|
||||
false_positives = [
|
||||
"""
|
||||
Legitimate users may travel, rotate through VPN egress IPs, or run automation from new build hosts, producing a
|
||||
first-seen IP for an existing access key. Baseline the principal, confirm with the key owner, and extend the history
|
||||
window or add exceptions for known automation networks if needed.
|
||||
""",
|
||||
]
|
||||
from = "now-6m"
|
||||
index = ["logs-aws.cloudtrail-*"]
|
||||
language = "kuery"
|
||||
license = "Elastic License v2"
|
||||
name = "AWS IAM Long-Term Access Key First Seen from Source IP"
|
||||
note = """## Triage and analysis
|
||||
|
||||
### Investigating AWS IAM Long-Term Access Key First Seen from Source IP
|
||||
|
||||
This rule is a [New Terms](https://www.elastic.co/guide/en/security/current/rules-ui-create.html#create-new-terms-rule) detection on CloudTrail. It fires when a successful API call uses a long-term IAM access key (`AKIA*`) from a `source.ip` that has not appeared with that key in the rule history window.
|
||||
|
||||
Long-term keys are high-value targets. Unlike session credentials (`ASIA*`), they do not expire until rotated or deleted. Threat reporting on cloud compromises often highlights abuse of leaked or stolen `AKIA` keys.
|
||||
|
||||
### Possible investigation steps
|
||||
|
||||
**Confirm the key and principal**
|
||||
- Identify the IAM user or root context implied by `aws.cloudtrail.user_identity.arn` or `user.name`.
|
||||
- **`aws.cloudtrail.user_identity.type`**: Distinguish `IAMUser`, `Root`, or other types; root long-term keys warrant extra scrutiny.
|
||||
|
||||
**Assess the new source**
|
||||
- **`source.ip`** and **`source.geo`**: Compare to normal geography, corporate egress, and known cloud provider ranges.
|
||||
- **`user_agent.original`**: Identify AWS CLI, SDKs, custom tooling, or unusual agents.
|
||||
|
||||
**Correlate activity**
|
||||
- Search CloudTrail for the same access key and IP over the following hours for sensitive APIs (IAM changes, STS, S3 data access, Secrets Manager, role assumption).
|
||||
- Review IAM last-used metadata for the key in the AWS console or API (`GetAccessKeyLastUsed`).
|
||||
|
||||
### False positive analysis
|
||||
|
||||
- Travel and VP* for human IAM users.
|
||||
- New CI runners, ephemeral build agents, or re-IP'd NAT gateways for automation keys.
|
||||
- Partner or MSP access from new networks if keys are shared (discouraged practice).
|
||||
|
||||
### Response and remediation
|
||||
|
||||
- If unexpected, deactivate or delete the access key, rotate credentials, and review policies attached to the user.
|
||||
- Enable or enforce MFA for console users; prefer roles and temporary credentials over long-term keys for workloads.
|
||||
- Document approved networks or principals and tune history or exceptions accordingly.
|
||||
|
||||
### Additional information
|
||||
|
||||
- [AWS Security Incident Response Guide](https://docs.aws.amazon.com/whitepapers/latest/aws-security-incident-response-guide/aws-security-incident-response-guide.pdf)
|
||||
|
||||
"""
|
||||
references = [
|
||||
"https://kudelskisecurity.com/research/investigating-two-variants-of-the-trivy-supply-chain-compromise",
|
||||
]
|
||||
risk_score = 47
|
||||
rule_id = "9f8e3c5e-f72e-4e91-93f6-e98a4fae3e4f"
|
||||
severity = "medium"
|
||||
tags = [
|
||||
"Domain: Cloud",
|
||||
"Data Source: AWS",
|
||||
"Data Source: Amazon Web Services",
|
||||
"Data Source: AWS CloudTrail",
|
||||
"Data Source: AWS IAM",
|
||||
"Use Case: Threat Detection",
|
||||
"Tactic: Credential Access",
|
||||
"Tactic: Initial Access",
|
||||
"Resources: Investigation Guide",
|
||||
]
|
||||
timestamp_override = "event.ingested"
|
||||
type = "new_terms"
|
||||
|
||||
query = '''
|
||||
event.dataset: "aws.cloudtrail"
|
||||
and event.outcome: "success"
|
||||
and source.ip:*
|
||||
and aws.cloudtrail.user_identity.access_key_id: AKIA*
|
||||
'''
|
||||
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1552"
|
||||
name = "Unsecured Credentials"
|
||||
reference = "https://attack.mitre.org/techniques/T1552/"
|
||||
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0006"
|
||||
name = "Credential Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0006/"
|
||||
|
||||
[[rule.threat]]
|
||||
framework = "MITRE ATT&CK"
|
||||
[[rule.threat.technique]]
|
||||
id = "T1078"
|
||||
name = "Valid Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/"
|
||||
[[rule.threat.technique.subtechnique]]
|
||||
id = "T1078.004"
|
||||
name = "Cloud Accounts"
|
||||
reference = "https://attack.mitre.org/techniques/T1078/004/"
|
||||
|
||||
[rule.threat.tactic]
|
||||
id = "TA0001"
|
||||
name = "Initial Access"
|
||||
reference = "https://attack.mitre.org/tactics/TA0001/"
|
||||
|
||||
[rule.investigation_fields]
|
||||
field_names = [
|
||||
"@timestamp",
|
||||
"user.name",
|
||||
"user_agent.original",
|
||||
"source.ip",
|
||||
"source.geo.city_name",
|
||||
"source.geo.country_iso_code",
|
||||
"source.as.organization.name",
|
||||
"aws.cloudtrail.user_identity.arn",
|
||||
"aws.cloudtrail.user_identity.type",
|
||||
"aws.cloudtrail.user_identity.access_key_id",
|
||||
"event.action",
|
||||
"event.outcome",
|
||||
"cloud.account.id",
|
||||
"cloud.region",
|
||||
]
|
||||
|
||||
[rule.new_terms]
|
||||
field = "new_terms_fields"
|
||||
value = ["aws.cloudtrail.user_identity.access_key_id", "source.ip"]
|
||||
[[rule.new_terms.history_window_start]]
|
||||
field = "history_window_start"
|
||||
value = "now-10d"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user