AWS Bedrock Model Invocation From High Number of Countries

edit
IMPORTANT: This documentation is no longer updated. Refer to Elastic's version policy and the latest documentation.

AWS Bedrock Model Invocation From High Number of Countries

edit

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.

Rule type: esql

Rule indices: None

Severity: critical

Risk score: 99

Runs every: 15m

Searches indices from: now-4h (Date Math format, see also Additional look-back time)

Maximum alerts per execution: 100

References:

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

Version: 1

Rule authors:

  • Elastic

Rule license: Elastic License v2

Investigation guide

edit

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.

Setup

edit

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.

Rule query

edit
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

Framework: MITRE ATT&CKTM