Detection rules › Kusto
GCP Audit Logs - Storage Bucket Made Public
Detects when a Google Cloud Storage bucket is made publicly accessible by granting permissions to allUsers or allAuthenticatedUsers. Making buckets public can expose sensitive data to unauthorized access and may indicate a misconfiguration or malicious activity. Adversaries may make buckets public to exfiltrate data or as part of a data exposure attack. This rule monitors setIamPermissions operations that add public access roles to storage buckets.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1078.004 Valid Accounts: Cloud Accounts |
| Collection | T1530 Data from Cloud Storage |
| Exfiltration | T1567.002 Exfiltration Over Web Service: Exfiltration to Cloud Storage |
Event coverage
| Provider | Event | Title |
|---|---|---|
| GCP-storage.googleapis.com | storage-setIamPermissions | Set IAM permissions on bucket |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- GCP Storage Bucket Opened To Public (YARA-L)
Rule body kusto
id: 3a8d7f9e-4b2c-4e5d-8c6b-9f1a3d5e8c7b
name: GCP Audit Logs - Storage Bucket Made Public
description: |
'Detects when a Google Cloud Storage bucket is made publicly accessible by granting permissions to allUsers or allAuthenticatedUsers.
Making buckets public can expose sensitive data to unauthorized access and may indicate a misconfiguration or malicious activity.
Adversaries may make buckets public to exfiltrate data or as part of a data exposure attack.
This rule monitors setIamPermissions operations that add public access roles to storage buckets.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
- Collection
- InitialAccess
- Exfiltration
relevantTechniques:
- T1530
- T1078.004
- T1567.002
tags:
- GCP
- Storage
- Data Exposure
- Cloud Security
query: |
GCPAuditLogs
| where ServiceName == "storage.googleapis.com"
| where MethodName == "storage.setIamPermissions"
| where GCPResourceType == "gcs_bucket"
| extend
ServiceDataJson = parse_json(ServiceData),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo),
AuthzInfoJson = parse_json(AuthorizationInfo)
| extend PolicyDelta = ServiceDataJson.policyDelta.bindingDeltas
| mv-expand PolicyDelta
| extend
Action = tostring(PolicyDelta.action),
Member = tostring(PolicyDelta.member),
Role = tostring(PolicyDelta.role)
| where Action == "ADD"
| where Member in~ ("allUsers", "allAuthenticatedUsers")
| extend
BucketName = extract(@"buckets/([^/]+)", 1, GCPResourceName),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
AuthEmail = tostring(AuthInfoJson.principalEmail),
Permission = tostring(AuthzInfoJson[0].permission),
PermissionGranted = tostring(AuthzInfoJson[0].granted)
| extend
PublicAccessType = case(
Member =~ "allUsers", "Public to Everyone",
Member =~ "allAuthenticatedUsers", "Public to All Authenticated Users",
"Unknown"),
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
BucketName,
ResourceName = GCPResourceName,
PublicAccessType,
Member,
Role,
CallerIpAddress,
UserAgent,
MethodName,
ServiceName,
Severity,
Permission,
PermissionGranted,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PrincipalEmail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: CallerIpAddress
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ProjectId
- identifier: InstanceName
columnName: ResourceName
customDetails:
ProjectId: ProjectId
BucketName: BucketName
ResourceName: ResourceName
PublicAccessType: PublicAccessType
RoleGranted: Role
UserAgent: UserAgent
Permission: Permission
alertDetailsOverride:
alertDisplayNameFormat: "GCP Storage Bucket {{BucketName}} Made Public by {{PrincipalEmail}}"
alertDescriptionFormat: |-
User {{PrincipalEmail}} made storage bucket {{BucketName}} publicly accessible in project {{ProjectId}}.
This may expose sensitive data to unauthorized access. Investigate immediately to determine if this action was authorized and assess potential data exposure.
Review bucket contents and access logs for any unauthorized access attempts.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Stage 1: source
GCPAuditLogs
Stage 2: where
| where ServiceName == "storage.googleapis.com"
Stage 3: where
| where MethodName == "storage.setIamPermissions"
Stage 4: where
| where GCPResourceType == "gcs_bucket"
Stage 5: extend
| extend
ServiceDataJson = parse_json(ServiceData),
RequestMetadataJson = parse_json(RequestMetadata),
AuthInfoJson = parse_json(AuthenticationInfo),
AuthzInfoJson = parse_json(AuthorizationInfo)
Stage 6: extend
| extend PolicyDelta = ServiceDataJson.policyDelta.bindingDeltas
Stage 7: mv-expand
| mv-expand PolicyDelta
Stage 8: extend
| extend
Action = tostring(PolicyDelta.action),
Member = tostring(PolicyDelta.member),
Role = tostring(PolicyDelta.role)
Stage 9: where
| where Action == "ADD"
Stage 10: where
| where Member in~ ("allUsers", "allAuthenticatedUsers")
Stage 11: extend
| extend
BucketName = extract(@"buckets/([^/]+)", 1, GCPResourceName),
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
AuthEmail = tostring(AuthInfoJson.principalEmail),
Permission = tostring(AuthzInfoJson[0].permission),
PermissionGranted = tostring(AuthzInfoJson[0].granted)
Stage 12: extend
| extend
PublicAccessType = case(
Member =~ "allUsers", "Public to Everyone",
Member =~ "allAuthenticatedUsers", "Public to All Authenticated Users",
"Unknown"),
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
PublicAccessType =Member =~ "allUsers""Public to Everyone"Member =~ "allAuthenticatedUsers""Public to All Authenticated Users""Unknown"Stage 13: project
| project TimeGenerated,
PrincipalEmail,
AuthEmail,
ProjectId,
BucketName,
ResourceName = GCPResourceName,
PublicAccessType,
Member,
Role,
CallerIpAddress,
UserAgent,
MethodName,
ServiceName,
Severity,
Permission,
PermissionGranted,
LogName,
InsertId,
AccountName,
AccountUPNSuffix
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
Action | eq |
|
GCPResourceType | eq |
|
Member | in |
|
MethodName | eq |
|
ServiceName | eq |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AccountName | project |
AccountUPNSuffix | project |
AuthEmail | project |
BucketName | project |
CallerIpAddress | project |
InsertId | project |
LogName | project |
Member | project |
MethodName | project |
Permission | project |
PermissionGranted | project |
PrincipalEmail | project |
ProjectId | project |
PublicAccessType | project |
ResourceName | project |
Role | project |
ServiceName | project |
Severity | project |
TimeGenerated | project |
UserAgent | project |