Query Details

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,
    AwsEventId

Explanation

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:

  1. 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.

  2. 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.

  3. Check for Risky Bucket ACL Changes:

    • It looks for events where the S3 bucket or object Access Control List (ACL) is modified (PutBucketAcl or PutObjectAcl events).
    • 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".
  4. Check for Risky Bucket Policies:

    • It examines events where bucket policies are set or modified (PutBucketPolicy or PutAccessPointPolicy events).
    • 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.
  5. 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 profile picture

Jose Sebastián Canós

Released: April 15, 2024

Tables

AWSCloudTrail

Keywords

AWSCloudTrail

Operators

setlettoscalar_GetWatchlistwherehassummarizemake_listinextendtodynamictostringhas_anymv-expandiffisemptyarray_lengthpack_arrayunioninvokeproject

Actions

GitHub