AWS Bedrock Model Invocation From High Number of Countries


Description

Detects a single AWS principal invoking Amazon Bedrock models from three or more countries and three or more source IPs within the lookback window. LLMjacking operators often distribute stolen AWS credentials across proxies or resell them to multiple buyers, which produces inference traffic for one identity from many unrelated locations.

Query · esql

from logs-aws.cloudtrail-*
| where
    data_stream.dataset == "aws.cloudtrail" and
    event.provider == "bedrock.amazonaws.com" and
    event.action in ("InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream") and
    event.outcome == "success" and
    aws.cloudtrail.user_identity.arn is not null and
    source.ip is not null and
    source.geo.country_name is not null
| eval
    Esql.identity = coalesce(
        aws.cloudtrail.user_identity.session_context.session_issuer.arn,
        aws.cloudtrail.user_identity.arn
    )
| stats
    Esql.country_count = count_distinct(source.geo.country_name),
    Esql.source_ip_count = count_distinct(source.ip),
    Esql.user_agent_count = count_distinct(user_agent.original),
    Esql.event_count = count(*),
    Esql.event_action_values = values(event.action),
    Esql.source_ip_values = values(source.ip),
    Esql.source_geo_country_name_values = values(source.geo.country_name),
    Esql.source_geo_city_name_values = values(source.geo.city_name),
    Esql.source_as_organization_name_values = values(source.as.organization.name),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.aws_cloudtrail_user_identity_arn_values = values(aws.cloudtrail.user_identity.arn),
    Esql.aws_cloudtrail_user_identity_type_values = values(aws.cloudtrail.user_identity.type),
    Esql.aws_cloudtrail_user_identity_access_key_id_values = values(aws.cloudtrail.user_identity.access_key_id),
    Esql.cloud_region_values = values(cloud.region),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp)
  by
    Esql.identity,
    cloud.account.id
| where Esql.country_count >= 3 and Esql.source_ip_count >= 3
| eval source.ip = mv_first(Esql.source_ip_values)
| keep
    Esql.identity,
    cloud.account.id,
    source.ip,
    Esql.country_count,
    Esql.source_ip_count,
    Esql.user_agent_count,
    Esql.event_count,
    Esql.event_action_values,
    Esql.source_ip_values,
    Esql.source_geo_country_name_values,
    Esql.source_geo_city_name_values,
    Esql.source_as_organization_name_values,
    Esql.user_agent_original_values,
    Esql.aws_cloudtrail_user_identity_arn_values,
    Esql.aws_cloudtrail_user_identity_type_values,
    Esql.aws_cloudtrail_user_identity_access_key_id_values,
    Esql.cloud_region_values,
    Esql.timestamp_first_seen,
    Esql.timestamp_last_seen

Investigation fields

Pivot points the source recommends for triage.

  • Esql.identity
  • cloud.account.id
  • source.ip
  • Esql.country_count
  • Esql.source_ip_count
  • Esql.user_agent_count
  • Esql.event_count
  • Esql.source_geo_country_name_values
  • Esql.source_ip_values
  • Esql.source_as_organization_name_values
  • Esql.user_agent_original_values
  • Esql.aws_cloudtrail_user_identity_arn_values
  • Esql.aws_cloudtrail_user_identity_access_key_id_values

Implementation guide

This rule requires AWS CloudTrail management events ingested via the AWS integration (aws.cloudtrail data stream) with source.geo.country_name populated by the integration's GeoIP enrichment. Bedrock runtime operations are logged as management events by default; no Bedrock model invocation logging is required.

Known false positives

  • Globally distributed applications or CI runners that call Bedrock with one shared IAM user or role. Prefer per-region identities, or raise the country threshold for these principals.
  • Users on VPNs or privacy relays that rotate exit countries. Corroborate with ASN and user agent before acting.

Analyst notes

Investigating AWS Bedrock Model Invocation From High Number of Countries

One AWS principal (role issuer ARN when present, otherwise aws.cloudtrail.user_identity.arn) invoked Amazon Bedrock models from at least three countries and three source IPs inside the lookback window. Assumed-role sessions are grouped by the issuing role so re-assumes of the same role still correlate. This pattern fits credentials that have been shared, resold, or proxied for LLMjacking.

Possible investigation steps

  • Review Esql.source_geo_country_name_values, Esql.source_ip_values, and Esql.source_as_organization_name_values. Residential proxies and hosting ASNs across several countries are more suspicious than one corporate VPN provider.
  • Check Esql.aws_cloudtrail_user_identity_access_key_id_values and Esql.aws_cloudtrail_user_identity_type_values. A single long-term AKIA key seen in many countries is a strong indicator of credential theft.
  • Compare Esql.user_agent_original_values. Several distinct clients for one identity often means several operators.
  • Search CloudTrail for the principal's other activity in the window, especially model access changes, guardrail or logging changes, and IAM changes.
  • Review Bedrock cost and usage for the account over the same period.

False positive analysis

  • Shared identities for global workloads. Move these to region-scoped roles or add an exception for the principal.
  • VPN and privacy relay users. Confirm with the account owner before rotating credentials.

Response and remediation

  • If the activity is not explained, deactivate the access key or revoke the role session, rotate credentials, and restrict bedrock:InvokeModel* permissions.
  • Set AWS Budgets or service quotas to limit the cost of further abuse.
Raw source AWS Bedrock Model Invocation From High Number of Countries · Elastic TOML
Esc
Published by elastic/detection-rules ↗, licensed under Elastic License 2.0 ↗. Reproduced here unmodified.
[metadata]
creation_date = "2026/10/23"
integration = ["aws"]
maturity = "production"
updated_date = "2026/10/23"

[rule]
author = ["Elastic"]
description = """
Detects a single AWS principal invoking Amazon Bedrock models from three or more countries and three or more source IPs
within the lookback window. LLMjacking operators often distribute stolen AWS credentials across proxies or resell them
to multiple buyers, which produces inference traffic for one identity from many unrelated locations.
"""
false_positives = [
    """
    Globally distributed applications or CI runners that call Bedrock with one shared IAM user or role. Prefer
    per-region identities, or raise the country threshold for these principals.
    """,
    """
    Users on VPNs or privacy relays that rotate exit countries. Corroborate with ASN and user agent before acting.
    """,
]
from = "now-4h"
interval = "15m"
language = "esql"
license = "Elastic License v2"
name = "AWS Bedrock Model Invocation From High Number of Countries"
note = """## Triage and analysis

### Investigating AWS Bedrock Model Invocation From High Number of Countries

One AWS principal (role issuer ARN when present, otherwise `aws.cloudtrail.user_identity.arn`) invoked Amazon Bedrock
models from at least three countries and three source IPs inside the lookback window. Assumed-role sessions are grouped
by the issuing role so re-assumes of the same role still correlate. This pattern fits credentials that have been shared,
resold, or proxied for LLMjacking.

#### Possible investigation steps

- Review `Esql.source_geo_country_name_values`, `Esql.source_ip_values`, and
  `Esql.source_as_organization_name_values`. Residential proxies and hosting ASNs across several countries are more
  suspicious than one corporate VPN provider.
- Check `Esql.aws_cloudtrail_user_identity_access_key_id_values` and `Esql.aws_cloudtrail_user_identity_type_values`.
  A single long-term `AKIA` key seen in many countries is a strong indicator of credential theft.
- Compare `Esql.user_agent_original_values`. Several distinct clients for one identity often means several operators.
- Search CloudTrail for the principal's other activity in the window, especially model access changes, guardrail or
  logging changes, and IAM changes.
- Review Bedrock cost and usage for the account over the same period.

### False positive analysis

- Shared identities for global workloads. Move these to region-scoped roles or add an exception for the principal.
- VPN and privacy relay users. Confirm with the account owner before rotating credentials.

### Response and remediation

- If the activity is not explained, deactivate the access key or revoke the role session, rotate credentials, and
  restrict `bedrock:InvokeModel*` permissions.
- Set AWS Budgets or service quotas to limit the cost of further abuse.
"""
references = [
    "https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html",
    "https://www.elastic.co/docs/reference/integrations/aws/cloudtrail",
]
risk_score = 99
rule_id = "a81fdc25-58a0-4306-a73d-850e9f7fe9eb"
setup = """## Setup

This rule requires AWS CloudTrail management events ingested via the AWS integration (`aws.cloudtrail` data stream)
with `source.geo.country_name` populated by the integration's GeoIP enrichment. Bedrock runtime operations are logged
as management events by default; no Bedrock model invocation logging is required.
"""
severity = "critical"
tags = [
    "Domain: Cloud",
    "Domain: GenAI",
    "Data Source: AWS",
    "Data Source: Amazon Web Services",
    "Data Source: AWS CloudTrail",
    "Platform: AWS",
    "Service: AWS Bedrock",
    "Use Case: Threat Detection",
    "Tactic: Credential Access",
    "Tactic: Initial Access",
    "Threat: Impossible Travel",
    "Threat: LLMjacking",
    "Threat: Unauthorized AI Usage",
    "Mitre Atlas: AML.T0012",
    "Mitre Atlas: AML.T0091",
    "Mitre Atlas: AML.T0091.000",
    "Resources: Investigation Guide",
    "Rule Type: ES|QL",
]
timestamp_override = "event.ingested"
type = "esql"

query = '''
from logs-aws.cloudtrail-*
| where
    data_stream.dataset == "aws.cloudtrail" and
    event.provider == "bedrock.amazonaws.com" and
    event.action in ("InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream") and
    event.outcome == "success" and
    aws.cloudtrail.user_identity.arn is not null and
    source.ip is not null and
    source.geo.country_name is not null
| eval
    Esql.identity = coalesce(
        aws.cloudtrail.user_identity.session_context.session_issuer.arn,
        aws.cloudtrail.user_identity.arn
    )
| stats
    Esql.country_count = count_distinct(source.geo.country_name),
    Esql.source_ip_count = count_distinct(source.ip),
    Esql.user_agent_count = count_distinct(user_agent.original),
    Esql.event_count = count(*),
    Esql.event_action_values = values(event.action),
    Esql.source_ip_values = values(source.ip),
    Esql.source_geo_country_name_values = values(source.geo.country_name),
    Esql.source_geo_city_name_values = values(source.geo.city_name),
    Esql.source_as_organization_name_values = values(source.as.organization.name),
    Esql.user_agent_original_values = values(user_agent.original),
    Esql.aws_cloudtrail_user_identity_arn_values = values(aws.cloudtrail.user_identity.arn),
    Esql.aws_cloudtrail_user_identity_type_values = values(aws.cloudtrail.user_identity.type),
    Esql.aws_cloudtrail_user_identity_access_key_id_values = values(aws.cloudtrail.user_identity.access_key_id),
    Esql.cloud_region_values = values(cloud.region),
    Esql.timestamp_first_seen = min(@timestamp),
    Esql.timestamp_last_seen = max(@timestamp)
  by
    Esql.identity,
    cloud.account.id
| where Esql.country_count >= 3 and Esql.source_ip_count >= 3
| eval source.ip = mv_first(Esql.source_ip_values)
| keep
    Esql.identity,
    cloud.account.id,
    source.ip,
    Esql.country_count,
    Esql.source_ip_count,
    Esql.user_agent_count,
    Esql.event_count,
    Esql.event_action_values,
    Esql.source_ip_values,
    Esql.source_geo_country_name_values,
    Esql.source_geo_city_name_values,
    Esql.source_as_organization_name_values,
    Esql.user_agent_original_values,
    Esql.aws_cloudtrail_user_identity_arn_values,
    Esql.aws_cloudtrail_user_identity_type_values,
    Esql.aws_cloudtrail_user_identity_access_key_id_values,
    Esql.cloud_region_values,
    Esql.timestamp_first_seen,
    Esql.timestamp_last_seen
'''


[[rule.threat]]
framework = "MITRE ATT&CK"
[[rule.threat.technique]]
id = "T1528"
name = "Steal Application Access Token"
reference = "https://attack.mitre.org/techniques/T1528/"


[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.threat_mappings]]
framework = "MITRE ATLAS"
version = "2026.09"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0012"
name = "Valid Accounts"
reference = "https://atlas.mitre.org/techniques/AML.T0012/"


[rule.threat_mappings.threat.tactic]
id = "AML.TA0004"
name = "Initial Access"
reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
[[rule.threat_mappings.threat]]
framework = "MITRE ATLAS"
[[rule.threat_mappings.threat.technique]]
id = "AML.T0091"
name = "Use Alternate Authentication Material"
reference = "https://atlas.mitre.org/techniques/AML.T0091/"
[[rule.threat_mappings.threat.technique.subtechnique]]
id = "AML.T0091.000"
name = "Application Access Token"
reference = "https://atlas.mitre.org/techniques/AML.T0091.000/"



[rule.threat_mappings.threat.tactic]
id = "AML.TA0015"
name = "Lateral Movement"
reference = "https://atlas.mitre.org/tactics/AML.TA0015/"

[rule.alert_suppression]
group_by = ["Esql.identity"]
missing_fields_strategy = "suppress"

[rule.investigation_fields]
field_names = [
    "Esql.identity",
    "cloud.account.id",
    "source.ip",
    "Esql.country_count",
    "Esql.source_ip_count",
    "Esql.user_agent_count",
    "Esql.event_count",
    "Esql.source_geo_country_name_values",
    "Esql.source_ip_values",
    "Esql.source_as_organization_name_values",
    "Esql.user_agent_original_values",
    "Esql.aws_cloudtrail_user_identity_arn_values",
    "Esql.aws_cloudtrail_user_identity_access_key_id_values",
]

[rule.alert_suppression.duration]
unit = "h"
value = 4

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.