Detection rules › Kusto
GCP Audit Logs - Detect Bulk VM Snapshot Deletion
'Detects bulk deletion of Google Cloud VM snapshots within a short time period, which may indicate data destruction or defense evasion activities. VM snapshots are critical for backup and disaster recovery. Bulk deletion of snapshots can prevent recovery from incidents and may indicate malicious activity such as ransomware, data destruction, or an attempt to cover tracks after a security breach. Adversaries may delete snapshots to maximize damage, prevent forensic investigation, or hinder recovery efforts. This rule triggers when multiple snapshots are deleted by the same user within a 1-minute window.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Stealth | T1562.001 Impair Defenses: Disable or Modify Tools |
| Impact | T1485 Data Destruction, T1490 Inhibit System Recovery |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
Rule body kusto
id: dfdffdc7-929f-4c7e-8f48-30e5ffddb067
name: GCP Audit Logs - Detect Bulk VM Snapshot Deletion
description: |
'Detects bulk deletion of Google Cloud VM snapshots within a short time period, which may indicate data destruction or defense evasion activities.
VM snapshots are critical for backup and disaster recovery. Bulk deletion of snapshots can prevent recovery from incidents and may indicate
malicious activity such as ransomware, data destruction, or an attempt to cover tracks after a security breach.
Adversaries may delete snapshots to maximize damage, prevent forensic investigation, or hinder recovery efforts.
This rule triggers when multiple snapshots are deleted by the same user within a 1-minute window.'
severity: High
status: Available
requiredDataConnectors:
- connectorId: GCPAuditLogsDefinition
dataTypes:
- GCPAuditLogs
queryFrequency: 15m
queryPeriod: 15m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Impact
- DefenseEvasion
relevantTechniques:
- T1485
- T1490
- T1562.001
tags:
- GCP
- Compute
- Data Destruction
- Cloud Security
query: |
// Update these thresholds if noisy in your environment
let SnapshotDeletionThreshold = 10;
let TimeWindow = 1m;
GCPAuditLogs
| where ServiceName == "compute.googleapis.com"
| where MethodName has "compute.snapshots.delete"
| where GCPResourceType == "gce_snapshot" and Severity == "NOTICE"
| extend
AuthzInfoJson = parse_json(AuthorizationInfo),
RequestMetadataJson = parse_json(RequestMetadata),
ResponseJson = parse_json(Response)
| extend PermissionType = tostring(AuthzInfoJson[0].permissionType)
| where PermissionType == "ADMIN_WRITE"
| extend
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
SnapshotName = extract(@"snapshots/([^/]+)", 1, GCPResourceName),
OperationType = tostring(ResponseJson.operationType),
OperationId = tostring(ResponseJson.id)
| summarize
SnapshotCount = count(),
SnapshotList = make_set(SnapshotName, 100),
FirstDeletion = min(TimeGenerated),
LastDeletion = max(TimeGenerated),
OperationIds = make_set(OperationId, 100),
CallerIPs = make_set(CallerIpAddress, 10)
by PrincipalEmail, ProjectId, UserAgent
| where SnapshotCount >= SnapshotDeletionThreshold
| extend DeletionTimeSpan = LastDeletion - FirstDeletion
| where DeletionTimeSpan <= TimeWindow
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
| project
TimeGenerated = FirstDeletion,
PrincipalEmail,
ProjectId,
SnapshotCount,
SnapshotList,
FirstDeletion,
LastDeletion,
DeletionTimeSpan,
CallerIPs,
UserAgent,
OperationIds,
AccountName,
AccountUPNSuffix
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: PrincipalEmail
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: CloudApplication
fieldMappings:
- identifier: Name
columnName: ProjectId
customDetails:
ProjectId: ProjectId
SnapshotCount: SnapshotCount
SnapshotList: SnapshotList
FirstDeletion: FirstDeletion
LastDeletion: LastDeletion
DeletionTimeSpan: DeletionTimeSpan
CallerIPs: CallerIPs
UserAgent: UserAgent
alertDetailsOverride:
alertDisplayNameFormat: "Bulk VM Snapshot Deletion: {{SnapshotCount}} snapshots deleted by {{PrincipalEmail}} in {{ProjectId}}"
alertDescriptionFormat: |-
User {{PrincipalEmail}} deleted {{SnapshotCount}} VM snapshots in project {{ProjectId}} within a short time period.
This may indicate ransomware, data destruction, or defense evasion activity. Verify authorization, check remaining snapshots, review IP addresses, and investigate for compromise.
version: 1.0.0
kind: Scheduled
Stages and Predicates
Parameters
let SnapshotDeletionThreshold = 10;
let TimeWindow = 1m;
Stage 1: source
GCPAuditLogs
Stage 2: where
| where ServiceName == "compute.googleapis.com"
Stage 3: where
| where MethodName has "compute.snapshots.delete"
Stage 4: where
| where GCPResourceType == "gce_snapshot" and Severity == "NOTICE"
Stage 5: extend
| extend
AuthzInfoJson = parse_json(AuthorizationInfo),
RequestMetadataJson = parse_json(RequestMetadata),
ResponseJson = parse_json(Response)
Stage 6: extend
| extend PermissionType = tostring(AuthzInfoJson[0].permissionType)
Stage 7: where
| where PermissionType == "ADMIN_WRITE"
Stage 8: extend
| extend
CallerIpAddress = tostring(RequestMetadataJson.callerIp),
UserAgent = tostring(RequestMetadataJson.callerSuppliedUserAgent),
SnapshotName = extract(@"snapshots/([^/]+)", 1, GCPResourceName),
OperationType = tostring(ResponseJson.operationType),
OperationId = tostring(ResponseJson.id)
Stage 9: summarize
| summarize
SnapshotCount = count(),
SnapshotList = make_set(SnapshotName, 100),
FirstDeletion = min(TimeGenerated),
LastDeletion = max(TimeGenerated),
OperationIds = make_set(OperationId, 100),
CallerIPs = make_set(CallerIpAddress, 10)
by PrincipalEmail, ProjectId, UserAgent
Stage 10: where
| where SnapshotCount >= SnapshotDeletionThreshold
Stage 11: extend
| extend DeletionTimeSpan = LastDeletion - FirstDeletion
Stage 12: where
| where DeletionTimeSpan <= TimeWindow
Stage 13: extend
| extend
AccountName = tostring(split(PrincipalEmail, "@")[0]),
AccountUPNSuffix = tostring(split(PrincipalEmail, "@")[1])
Stage 14: project
| project
TimeGenerated = FirstDeletion,
PrincipalEmail,
ProjectId,
SnapshotCount,
SnapshotList,
FirstDeletion,
LastDeletion,
DeletionTimeSpan,
CallerIPs,
UserAgent,
OperationIds,
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 |
|---|---|---|
DeletionTimeSpan | le |
|
GCPResourceType | eq |
|
MethodName | match |
|
PermissionType | eq |
|
ServiceName | eq |
|
Severity | eq |
|
SnapshotCount | ge |
|
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 |
CallerIPs | project |
DeletionTimeSpan | project |
FirstDeletion | project |
LastDeletion | project |
OperationIds | project |
PrincipalEmail | project |
ProjectId | project |
SnapshotCount | project |
SnapshotList | project |
TimeGenerated | project |
UserAgent | project |