↳ GitHub sourceAnalytics ruleHigh
AWSCloudTrail - Successful brute force attack on S3 Bucket
Description
Detects repeated failed GetObject attempts against an S3 bucket followed by a successful access from the
same source, which can indicate brute-force-style object discovery or access attempts.
- Rule type
- Scheduled
- Version
- 1.0.2
- Declared status
- Available
- Query frequency
- 1h
- Query period
- 1h
- Trigger
- gt 0
Declared MITRE coverage
Declared sources
Metadata from the source file. No dependencies inferred from KQL.
Connectors
Data types
KQL query
Original query, unchanged.
let timeframe = 1h;
let failed_attempts = AWSCloudTrail
| where TimeGenerated >= ago(timeframe)
| where EventName == "GetObject" and isnotempty(ErrorMessage) and isnotempty(ErrorCode)
| where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
| summarize time_min_failed=arg_min(TimeGenerated, *), failed_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
| where failed_keys > 20;
let success_attempts = AWSCloudTrail
| where TimeGenerated >= ago(timeframe)
| where EventName == "GetObject" and isempty(ErrorMessage) and isempty(ErrorCode)
| where UserIdentityAccountId == "ANONYMOUS_PRINCIPAL" or UserIdentityAccessKeyId <> RecipientAccountId
| extend UserIdentityArn = iif(isempty(UserIdentityArn), tostring(parse_json(Resources)[0].ARN), UserIdentityArn)
| extend UserName = tostring(split(UserIdentityArn, '/')[-1])
| extend AccountName = case( UserIdentityPrincipalid == "Anonymous", "Anonymous", isempty(UserIdentityUserName), UserName, UserIdentityUserName)
| extend AccountName = iif(AccountName contains "@", tostring(split(AccountName, '@', 0)[0]), AccountName),
AccountUPNSuffix = iif(AccountName contains "@", tostring(split(AccountName, '@', 1)[0]), "")
| extend bucketName = tostring(parse_json(RequestParameters).bucketName), keyName = tostring(parse_json(RequestParameters).key)
| summarize time_min_success=arg_min(TimeGenerated, *), success_keys = dcount(keyName) by RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, SourceIpAddress, bucketName
| where success_keys >= 1;
failed_attempts
| join kind=inner success_attempts on SourceIpAddress, RecipientAccountId, AccountName, AccountUPNSuffix, UserIdentityAccountId, bucketName
| where time_min_success > time_min_failed
| project-away keyName
Declared entities
Related content
Links established from declared identifiers and solution manifests.
Source provenance
GitHubDisplayed values come from files in Azure/Azure-Sentinel. They describe the published template, not your workspace configuration.
- Commit
9800e51↗- Source identifier
31b9e94b-0df6-4a3d-a297-3457b53c5d86
GSTEP / CATALOG TRACKING
Added to catalog : 16 Sept 2026 · 05:49 UTC
Last change observed : 16 Sept 2026 · 05:49 UTC