AWS Cloud Trail AWS S3 Bucket Publicly Exposed
Query
// The query_now parameter represents the time (in UTC) at which the scheduled analytics rule ran to produce this alert.
set query_now = datetime(2024-04-15T14:13:46.6297420Z);
let _SharedAWSAccounts = toscalar(
_GetWatchlist("AccountId-AuditAWSAccounts")
| where Notes has "[ShareBucket]"
| summarize make_list(AccountId)
);
let _Headers_PutBucketAcl =
AWSCloudTrail
| where EventName in ("PutBucketAcl", "PutObjectAcl")
| extend RequestParameters = todynamic(RequestParameters)
| where tostring(RequestParameters["accessControlList"]["x-amz-grant-full-control"]) has_any ("AuthenticatedUsers", "AllUsers", "emailAddress=", "id=")
or tostring(RequestParameters["accessControlList"]["x-amz-grant-read"]) has_any ("AuthenticatedUsers", "AllUsers", "emailAddress=", "id=")
or tostring(RequestParameters["accessControlList"]["x-amz-grant-read-acp"]) has_any ("AuthenticatedUsers", "AllUsers", "emailAddress=", "id=")
or tostring(RequestParameters["accessControlList"]["x-amz-grant-write"]) has_any ("AuthenticatedUsers", "AllUsers", "emailAddress=", "id=")
or tostring(RequestParameters["accessControlList"]["x-amz-grant-write-acp"]) has_any ("AuthenticatedUsers", "AllUsers", "emailAddress=", "id=")
;
let _ACL_PutBucketAcl =
AWSCloudTrail
| where EventName in ("PutBucketAcl", "PutObjectAcl")
| extend RequestParameters = todynamic(RequestParameters)
| mv-expand Grant = iff(isempty(array_length(RequestParameters["AccessControlPolicy"]["AccessControlList"])), pack_array(RequestParameters["AccessControlPolicy"]["AccessControlList"]), RequestParameters["AccessControlPolicy"]["AccessControlList"])
| where Grant has_any ("AuthenticatedUsers", "AllUsers", "EmailAddress", "ID", "authenticated-read", "public-read", "public-read-write")
;
let _BucketPolicies =
AWSCloudTrail
| where EventName in ("PutBucketPolicy", "PutAccessPointPolicy")
| extend RequestParameters = todynamic(RequestParameters)
// | extend Statement = case(
// bag_keys(RequestParameters) has "bucketPolicy", todynamic(tostring(RequestParameters["bucketPolicy"]))["Statement"],
// bag_keys(RequestParameters) has "PutAccessPointPolicyRequest", todynamic(tostring(RequestParameters["PutAccessPointPolicyRequest"]))["Policy"]["Statement"],
// bag_keys(RequestParameters) has "policyDocument", todynamic(tostring(RequestParameters["policyDocument"]))["Statement"],
// bag_keys(RequestParameters) has "content", todynamic(tostring(RequestParameters["content"]))["Statement"],
// dynamic(null)
// )
// | mv-expand Statement = iff(isnotempty(bag_keys(Statement)), pack_array(Statement), Statement)
// | extend
// Principal = tostring(Statement["Principal"]),
// Effect = tostring(Statement["Effect"]),
// Action = tostring(Statement["Action"]),
// Resource = tostring(Statement["Resource"]),
// Condition = tostring(Statement["Condition"])
// | where Effect == "Allow"
// | where Action has "*" or (not(strcat(Principal, "|", Condition) has RecipientAccountId) and not(strcat(Principal, "|", Condition) has_any (_SharedAWSAccounts)))
// | summarize take_any(*) by AwsEventId
;
union _Headers_PutBucketAcl, _ACL_PutBucketAcl, _BucketPolicies
| invoke AWSIdentityRole()
| project
TimeGenerated,
UserIdentityType,
Identity,
ActorRole,
UserIdentityAccountId,
UserIdentityAccountName,
RecipientAccountId,
RecipientAccountName,
AWSRegion,
SessionCreationDate,
UserIdentityPrincipalid,
UserIdentityArn,
SourceIpAddress,
EventSource,
EventTypeName,
EventName,
ManagementEvent,
ReadOnly,
ErrorCode,
ErrorMessage,
RequestParameters,
ResponseElements,
Resources,
SessionMfaAuthenticated,
UserAgent,
AwsEventIdExplanation
This KQL query is designed to identify potentially risky changes to AWS S3 bucket permissions by analyzing AWS CloudTrail logs. Here's a simplified breakdown of what the query does:
-
Set a Reference Time: The query starts by setting a reference time (
query_now) to the current UTC time when the analytics rule is executed. -
Identify Shared AWS Accounts: It retrieves a list of AWS account IDs from a watchlist named "AccountId-AuditAWSAccounts" that are marked with "[ShareBucket]". These accounts are considered shared or potentially risky.
-
Check for Risky Bucket ACL Changes:
- It looks for events where the S3 bucket or object Access Control List (ACL) is modified (
PutBucketAclorPutObjectAclevents). - It checks if the ACL changes grant permissions to broad groups like "AuthenticatedUsers" or "AllUsers", or if they involve specific identifiers like "emailAddress" or "id".
- It looks for events where the S3 bucket or object Access Control List (ACL) is modified (
-
Check for Risky Bucket Policies:
- It examines events where bucket policies are set or modified (
PutBucketPolicyorPutAccessPointPolicyevents). - Although the detailed logic for analyzing bucket policies is commented out, the intention is to identify policies that allow wide access or involve accounts not in the shared accounts list.
- It examines events where bucket policies are set or modified (
-
Combine and Analyze Results:
- It combines the results from the ACL and policy checks.
- It uses a function (
AWSIdentityRole) to enrich the data with additional identity information. - Finally, it selects and displays relevant fields such as the time of the event, user identity details, AWS region, event type, and any error messages.
Overall, this query is designed to detect and alert on potentially insecure changes to S3 bucket permissions, which could expose data to unauthorized users.
Details

Jose Sebastián Canós
Released: April 15, 2024
Tables
Keywords
Operators