2026-10-31 — AWS Bedrock LLMJacking and Stolen AI Platform Credentials: Sentinel Hunts

Threat Overview

Wiz Research linked nine publicly disclosed October AI developer-platform incidents to a wider pattern of attackers seeking environment variables and cloud/AI credentials. In two confirmed downstream enterprise AWS cases, keys previously used through a breached provider were validated within 30 minutes, later enumerated across models and quotas, and abused for approximately 800,000 and 1.7 million Bedrock calls. Wiz does not establish a single shared vulnerability, root cause or attacker for all nine incidents.

References

Wiz Research — Black October for AI Developer Platforms (October 30, 2026)

AWS — Amazon Bedrock CloudTrail logging

AWS — Bedrock Mantle CloudTrail logging

Impacted Systems

AWS accounts with Amazon Bedrock model-invocation permissions and credentials stored at or used through third-party AI development and hosting services. Cloud-managed AWS Bedrock and optional Bedrock Mantle. No vulnerable software version or patch is identified: exploitation requires stolen usable credentials with Bedrock permissions. Affected vendor incidents reported during October include Fireworks AI, Andromeda, Zeabur, Lightning AI, OpenCode, Railway, Warp, CommandCode and Northflank; root causes differ and investigations remain ongoing.

Why this matters

Stolen legitimate cloud credentials can generate major inference charges without malware on the customer’s endpoints. AWS CloudTrail can identify unusual principals, source IPs, model sweeps, quota discovery and sustained inference calls.

Exploitation Status

Wiz confirmed two downstream Bedrock abuse cases totaling approximately 2.5 million model calls. Wiz observed key-checking activity from 23.106.46.239, 38.246.114.118, 104.243.32.50 and 31.57.201.201, but explicitly states these IPs are not proven initial-intrusion infrastructure for every platform incident. Relationships between resale organizations and original intrusions are not fully established.

What this hunt looks for

High-volume inference bursts, newly observed credential/IP pairs, model and quota enumeration, inference permission failures, published stolen-key checking infrastructure and Mantle inference spikes where data events are collected.

Required logs

AWSCloudTrail in Microsoft Sentinel, including Bedrock management events. Bedrock Mantle CreateInference is a CloudTrail data event that requires explicit configuration; standard Bedrock InvokeModel/Converse calls are management events. A CloudTrail integration alone does not guarantee all AWS accounts or event classes are ingested.

01 — Sudden high-volume Bedrock model invocation by credential and IP

AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","InvokeModelWithResponseStream","Converse","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where EventTypeValue == "" or EventTypeValue == "AwsApiCall"
| summarize Calls=count(), Models=make_set(extract(@'"modelId"\s*:\s*"([^"]+)',1,Params),15), Agents=make_set(UA,5) by Principal, SrcIP, bin(TimeGenerated,15m)
| where Calls >= 100
| order by Calls desc

02 — Newly observed IP for a Bedrock principal

let Base=AWSCloudTrail
| where TimeGenerated between (ago(30d) .. ago(1d))
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize by Principal, SrcIP;
AWSCloudTrail
| where TimeGenerated >= ago(1d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| join kind=leftanti Base on Principal, SrcIP
| summarize Calls=count(), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), UserAgents=make_set(UA,5) by Principal, SrcIP
| order by Calls desc

03 — Published stolen-key checking infrastructure

let IOCs=dynamic(["23.106.46.239","38.246.114.118","104.243.32.50","31.57.201.201"]);
AWSCloudTrail
| where TimeGenerated >= ago(30d)
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where SrcIP in (IOCs)
| project TimeGenerated, EventSource, EventName, Principal, SrcIP, UA, Params, ErrorCode, AWSRegion
| order by TimeGenerated desc

04 — Bedrock model and quota enumeration

AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventName in ("ListFoundationModels","ListInferenceProfiles","GetInferenceProfile","GetFoundationModel","GetServiceQuota","ListServiceQuotas","ListAWSDefaultServiceQuotas")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize Requests=count(), Operations=make_set(EventName,12), Regions=make_set(AWSRegion,10), UserAgents=make_set(UA,5) by Principal, SrcIP, bin(TimeGenerated,1h)
| where Requests >= 8 or array_length(Operations) >= 4
| order by Requests desc

05 — Multiple Bedrock models probed by one credential

AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| extend ModelId=tostring(parse_json(Params).modelId)
| where isnotempty(ModelId)
| summarize Calls=count(), DistinctModels=dcount(ModelId), Models=make_set(ModelId,20) by Principal, SrcIP, bin(TimeGenerated,1h)
| where DistinctModels >= 6
| order by DistinctModels desc

06 — Bedrock permission failures or denied inference profile probes

AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream","ListInferenceProfiles")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| extend Err=tostring(column_ifexists("ErrorCode", ""))
| where isnotempty(Err)
| summarize Denied=count(), Errors=make_set(Err,10), ParamsSamples=make_set(substring(Params,0,180),5) by Principal, SrcIP, bin(TimeGenerated,30m)
| where Denied >= 10
| order by Denied desc

07 — Bedrock quota discovery followed by inference activity

let Quotas=AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventName in ("GetServiceQuota","ListServiceQuotas","ListAWSDefaultServiceQuotas","ListInferenceProfiles")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize QuotaEvents=count(), QuotaFirst=min(TimeGenerated) by Principal, SrcIP, Bucket=bin(TimeGenerated,1h);
let Calls=AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock.amazonaws.com" and EventName in ("InvokeModel","Converse","InvokeModelWithResponseStream","ConverseStream")
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| summarize InferenceCalls=count() by Principal, SrcIP, Bucket=bin(TimeGenerated,1h);
Quotas
| join kind=inner Calls on Principal, SrcIP, Bucket
| where InferenceCalls >= 50
| project Bucket, Principal, SrcIP, QuotaEvents, InferenceCalls, QuotaFirst
| order by InferenceCalls desc

08 — Bedrock Mantle inference burst (data-event coverage only)

AWSCloudTrail
| where TimeGenerated >= ago(14d)
| where EventSource == "bedrock-mantle.amazonaws.com" and EventName == "CreateInference"
| extend Principal=tostring(column_ifexists("UserIdentityArn", "")), SrcIP=tostring(column_ifexists("SourceIpAddress", "")), UA=tostring(column_ifexists("UserAgent", "")), Params=tostring(column_ifexists("RequestParameters", "")), EventTypeValue=tostring(column_ifexists("EventType", ""))
| where EventTypeValue == "" or EventTypeValue == "AwsApiCall"
| summarize Calls=count(), Agents=make_set(UA,5), Regions=make_set(AWSRegion,10) by Principal, SrcIP, bin(TimeGenerated,15m)
| where Calls >= 100
| order by Calls desc

Detection Notes

Highest signal is a new principal/IP combined with quota or model enumeration and a sustained inference burst. Thresholds need workload calibration. Streaming API completion events can duplicate invocation records; where EventType is available, count AwsApiCall only. Mantle uses bedrock-mantle.amazonaws.com and needs data-event capture. Wiz IPs are credential-checking/resale indicators, not confirmed initial access for all breaches. Missing AWSCloudTrail ingestion or omitted accounts prevents visibility. No customer-specific Sentinel query was run and no finding is asserted.

Leave a Reply