EXPLORE
← Back to Explore
elastichighTTP

AWS IAM Long-Term Access Key Correlated with Elevated Detection Alerts

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.

MITRE ATT&CK

credential-accessinitial-access

Detection 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

Author

Elastic

Created

2026/04/06

Data Sources

AWSAmazon Web ServicesAWS CloudTrailAWS IAM

Tags

Domain: CloudData Source: AWSData Source: Amazon Web ServicesData Source: AWS CloudTrailData Source: AWS IAMUse Case: Threat DetectionTactic: Credential AccessTactic: Initial AccessResources: Investigation GuideRule Type: Higher-Order Rule
Raw Content
[metadata]
creation_date = "2026/04/06"
integration = ["aws"]
maturity = "production"
updated_date = "2026/04/06"
min_stack_comments = "New entity classification fields added: user.entity.id"
min_stack_version = "9.2.0"

[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",
]