AWS Bedrock Model Invocation From High Number of Countries

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.

Elastic rule (View on GitHub)

  1[metadata]
  2creation_date = "2026/10/23"
  3integration = ["aws"]
  4maturity = "production"
  5updated_date = "2026/10/23"
  6
  7[rule]
  8author = ["Elastic"]
  9description = """
 10Detects a single AWS principal invoking Amazon Bedrock models from three or more countries and three or more source IPs
 11within the lookback window. LLMjacking operators often distribute stolen AWS credentials across proxies or resell them
 12to multiple buyers, which produces inference traffic for one identity from many unrelated locations.
 13"""
 14false_positives = [
 15    """
 16    Globally distributed applications or CI runners that call Bedrock with one shared IAM user or role. Prefer
 17    per-region identities, or raise the country threshold for these principals.
 18    """,
 19    """
 20    Users on VPNs or privacy relays that rotate exit countries. Corroborate with ASN and user agent before acting.
 21    """,
 22]
 23from = "now-4h"
 24interval = "15m"
 25language = "esql"
 26license = "Elastic License v2"
 27name = "AWS Bedrock Model Invocation From High Number of Countries"
 28note = """## Triage and analysis
 29
 30### Investigating AWS Bedrock Model Invocation From High Number of Countries
 31
 32One AWS principal (role issuer ARN when present, otherwise `aws.cloudtrail.user_identity.arn`) invoked Amazon Bedrock
 33models from at least three countries and three source IPs inside the lookback window. Assumed-role sessions are grouped
 34by the issuing role so re-assumes of the same role still correlate. This pattern fits credentials that have been shared,
 35resold, or proxied for LLMjacking.
 36
 37#### Possible investigation steps
 38
 39- Review `Esql.source_geo_country_name_values`, `Esql.source_ip_values`, and
 40  `Esql.source_as_organization_name_values`. Residential proxies and hosting ASNs across several countries are more
 41  suspicious than one corporate VPN provider.
 42- Check `Esql.aws_cloudtrail_user_identity_access_key_id_values` and `Esql.aws_cloudtrail_user_identity_type_values`.
 43  A single long-term `AKIA` key seen in many countries is a strong indicator of credential theft.
 44- Compare `Esql.user_agent_original_values`. Several distinct clients for one identity often means several operators.
 45- Search CloudTrail for the principal's other activity in the window, especially model access changes, guardrail or
 46  logging changes, and IAM changes.
 47- Review Bedrock cost and usage for the account over the same period.
 48
 49### False positive analysis
 50
 51- Shared identities for global workloads. Move these to region-scoped roles or add an exception for the principal.
 52- VPN and privacy relay users. Confirm with the account owner before rotating credentials.
 53
 54### Response and remediation
 55
 56- If the activity is not explained, deactivate the access key or revoke the role session, rotate credentials, and
 57  restrict `bedrock:InvokeModel*` permissions.
 58- Set AWS Budgets or service quotas to limit the cost of further abuse.
 59"""
 60references = [
 61    "https://docs.aws.amazon.com/bedrock/latest/userguide/logging-using-cloudtrail.html",
 62    "https://www.elastic.co/docs/reference/integrations/aws/cloudtrail",
 63]
 64risk_score = 99
 65rule_id = "a81fdc25-58a0-4306-a73d-850e9f7fe9eb"
 66setup = """## Setup
 67
 68This rule requires AWS CloudTrail management events ingested via the AWS integration (`aws.cloudtrail` data stream)
 69with `source.geo.country_name` populated by the integration's GeoIP enrichment. Bedrock runtime operations are logged
 70as management events by default; no Bedrock model invocation logging is required.
 71"""
 72severity = "critical"
 73tags = [
 74    "Domain: Cloud",
 75    "Domain: GenAI",
 76    "Data Source: AWS",
 77    "Data Source: Amazon Web Services",
 78    "Data Source: AWS CloudTrail",
 79    "Platform: AWS",
 80    "Service: AWS Bedrock",
 81    "Use Case: Threat Detection",
 82    "Tactic: Credential Access",
 83    "Tactic: Initial Access",
 84    "Threat: Impossible Travel",
 85    "Threat: LLMjacking",
 86    "Threat: Unauthorized AI Usage",
 87    "Mitre Atlas: AML.T0012",
 88    "Mitre Atlas: AML.T0091",
 89    "Mitre Atlas: AML.T0091.000",
 90    "Resources: Investigation Guide",
 91    "Rule Type: ES|QL",
 92]
 93timestamp_override = "event.ingested"
 94type = "esql"
 95
 96query = '''
 97from logs-aws.cloudtrail-*
 98| where
 99    data_stream.dataset == "aws.cloudtrail" and
100    event.provider == "bedrock.amazonaws.com" and
101    event.action in ("InvokeModel", "InvokeModelWithResponseStream", "Converse", "ConverseStream") and
102    event.outcome == "success" and
103    aws.cloudtrail.user_identity.arn is not null and
104    source.ip is not null and
105    source.geo.country_name is not null
106| eval
107    Esql.identity = coalesce(
108        aws.cloudtrail.user_identity.session_context.session_issuer.arn,
109        aws.cloudtrail.user_identity.arn
110    )
111| stats
112    Esql.country_count = count_distinct(source.geo.country_name),
113    Esql.source_ip_count = count_distinct(source.ip),
114    Esql.user_agent_count = count_distinct(user_agent.original),
115    Esql.event_count = count(*),
116    Esql.event_action_values = values(event.action),
117    Esql.source_ip_values = values(source.ip),
118    Esql.source_geo_country_name_values = values(source.geo.country_name),
119    Esql.source_geo_city_name_values = values(source.geo.city_name),
120    Esql.source_as_organization_name_values = values(source.as.organization.name),
121    Esql.user_agent_original_values = values(user_agent.original),
122    Esql.aws_cloudtrail_user_identity_arn_values = values(aws.cloudtrail.user_identity.arn),
123    Esql.aws_cloudtrail_user_identity_type_values = values(aws.cloudtrail.user_identity.type),
124    Esql.aws_cloudtrail_user_identity_access_key_id_values = values(aws.cloudtrail.user_identity.access_key_id),
125    Esql.cloud_region_values = values(cloud.region),
126    Esql.timestamp_first_seen = min(@timestamp),
127    Esql.timestamp_last_seen = max(@timestamp)
128  by
129    Esql.identity,
130    cloud.account.id
131| where Esql.country_count >= 3 and Esql.source_ip_count >= 3
132| eval source.ip = mv_first(Esql.source_ip_values)
133| keep
134    Esql.identity,
135    cloud.account.id,
136    source.ip,
137    Esql.country_count,
138    Esql.source_ip_count,
139    Esql.user_agent_count,
140    Esql.event_count,
141    Esql.event_action_values,
142    Esql.source_ip_values,
143    Esql.source_geo_country_name_values,
144    Esql.source_geo_city_name_values,
145    Esql.source_as_organization_name_values,
146    Esql.user_agent_original_values,
147    Esql.aws_cloudtrail_user_identity_arn_values,
148    Esql.aws_cloudtrail_user_identity_type_values,
149    Esql.aws_cloudtrail_user_identity_access_key_id_values,
150    Esql.cloud_region_values,
151    Esql.timestamp_first_seen,
152    Esql.timestamp_last_seen
153'''
154
155
156[[rule.threat]]
157framework = "MITRE ATT&CK"
158[[rule.threat.technique]]
159id = "T1528"
160name = "Steal Application Access Token"
161reference = "https://attack.mitre.org/techniques/T1528/"
162
163
164[rule.threat.tactic]
165id = "TA0006"
166name = "Credential Access"
167reference = "https://attack.mitre.org/tactics/TA0006/"
168[[rule.threat]]
169framework = "MITRE ATT&CK"
170[[rule.threat.technique]]
171id = "T1078"
172name = "Valid Accounts"
173reference = "https://attack.mitre.org/techniques/T1078/"
174[[rule.threat.technique.subtechnique]]
175id = "T1078.004"
176name = "Cloud Accounts"
177reference = "https://attack.mitre.org/techniques/T1078/004/"
178
179
180
181[rule.threat.tactic]
182id = "TA0001"
183name = "Initial Access"
184reference = "https://attack.mitre.org/tactics/TA0001/"
185[[rule.threat_mappings]]
186framework = "MITRE ATLAS"
187version = "2026.09"
188[[rule.threat_mappings.threat]]
189framework = "MITRE ATLAS"
190[[rule.threat_mappings.threat.technique]]
191id = "AML.T0012"
192name = "Valid Accounts"
193reference = "https://atlas.mitre.org/techniques/AML.T0012/"
194
195
196[rule.threat_mappings.threat.tactic]
197id = "AML.TA0004"
198name = "Initial Access"
199reference = "https://atlas.mitre.org/tactics/AML.TA0004/"
200[[rule.threat_mappings.threat]]
201framework = "MITRE ATLAS"
202[[rule.threat_mappings.threat.technique]]
203id = "AML.T0091"
204name = "Use Alternate Authentication Material"
205reference = "https://atlas.mitre.org/techniques/AML.T0091/"
206[[rule.threat_mappings.threat.technique.subtechnique]]
207id = "AML.T0091.000"
208name = "Application Access Token"
209reference = "https://atlas.mitre.org/techniques/AML.T0091.000/"
210
211
212
213[rule.threat_mappings.threat.tactic]
214id = "AML.TA0015"
215name = "Lateral Movement"
216reference = "https://atlas.mitre.org/tactics/AML.TA0015/"
217
218[rule.alert_suppression]
219group_by = ["Esql.identity"]
220missing_fields_strategy = "suppress"
221
222[rule.investigation_fields]
223field_names = [
224    "Esql.identity",
225    "cloud.account.id",
226    "source.ip",
227    "Esql.country_count",
228    "Esql.source_ip_count",
229    "Esql.user_agent_count",
230    "Esql.event_count",
231    "Esql.source_geo_country_name_values",
232    "Esql.source_ip_values",
233    "Esql.source_as_organization_name_values",
234    "Esql.user_agent_original_values",
235    "Esql.aws_cloudtrail_user_identity_arn_values",
236    "Esql.aws_cloudtrail_user_identity_access_key_id_values",
237]
238
239[rule.alert_suppression.duration]
240unit = "h"
241value = 4

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

Related rules

to-top