Detection rules › Kusto

Detect CoreBackUp Deletion Activity from related Security Alerts

Status
available
Severity
medium
Time window
1d
Group by
AlertName
Source
github.com/Azure/Azure-Sentinel

'The query identifies any efforts by an attacker to delete backup containers, while also searching for any security alerts that may be linked to the same activity, in order to uncover additional information about the attacker's actions.' Though such an activity could be legitimate as part of business operation, some ransomware actors may perform such operation to cause interruption to regular business services.'

MITRE ATT&CK coverage

TacticTechniques
ImpactT1496 Resource Hijacking

Rule body kusto

id: 011c84d8-85f0-4370-b864-24c13455aa94
name: Detect CoreBackUp Deletion Activity from related Security Alerts
description: |
  'The query identifies any efforts by an attacker to delete backup containers, while also searching for any security alerts that may be linked to the same activity, in order to uncover additional information about the attacker's actions.' 
  Though such an activity could be legitimate as part of business operation, some ransomware actors may perform such operation to cause interruption to regular business services.'
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: AzureSecurityCenter
    dataTypes:
      - SecurityAlert
  - connectorId: MicrosoftDefenderForCloudTenantBased
    dataTypes:
      - SecurityAlert
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1496
query: |
  SecurityAlert
  | extend Extprop = parse_json(ExtendedProperties)
  | mv-expand todynamic(Entities)
  | extend HostName = iff(isnotempty(tostring(Extprop["Compromised Host"])), tolower(tostring(Extprop["Compromised Host"])), tolower(tostring(parse_json(Entities).HostName)))
  | where isnotempty(HostName)
  | mv-expand todynamic(split(HostName, ','))
  | extend DnsDomain = iff(isnotempty(tostring(Extprop["Machine Domain"])), tostring(Extprop["Machine Domain"]), tostring(parse_json(Entities).DnsDomain))
  | extend UserName = iff(isnotempty(tostring(Extprop["User Name"])), tostring(Extprop["User Name"]), iff(tostring(parse_json(Entities).Type) == 'account', tostring(parse_json(Entities).Name), ''))
  | extend NTDomain = iff(isnotempty(tostring(Extprop["User Domain"])), tostring(Extprop["User Domain"]), tostring(parse_json(Entities).NTDomain))
  | extend IpAddress = iff(tostring(parse_json(Entities).Type) == 'ip', tostring(parse_json(Entities).Address), tostring(parse_json(Extprop).["IpAddress"]))
  | summarize timestamp = arg_max(TimeGenerated, *) by AlertName, tostring(HostName)
  | project timestamp, AlertName, UserName, NTDomain, tostring(HostName), DnsDomain, IpAddress
  | join kind=inner
  (
  CoreAzureBackup
  | where State =~ "Deleted"
  | where OperationName =~ "BackupItem"
  | extend data = split(BackupItemUniqueId, ";")
  | extend AzureLocation = data[0], VaultId=data[1], HostName=tolower(tostring(data[2])), DrivesBackedUp=data[3]
  | project timestamp = TimeGenerated, AzureLocation, VaultId, HostName, DrivesBackedUp, State, BackupItemUniqueId, _ResourceId, OperationName, BackupItemFriendlyName
  )
  on HostName
  | project timestamp, AlertName, HostName, DnsDomain, UserName, NTDomain, _ResourceId, IpAddress, VaultId, AzureLocation, DrivesBackedUp, State, BackupItemUniqueId, OperationName, BackupItemFriendlyName
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
      - identifier: NTDomain
        columnName: NTDomain
  - entityType: AzureResource
    fieldMappings:
      - identifier: ResourceId
        columnName: _ResourceId
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: HostName
      - identifier: DnsDomain
        columnName: DnsDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.0.2
kind: Scheduled

Stages and Predicates

Stage 1: source

SecurityAlert

Stage 2: extend

| extend Extprop = parse_json(ExtendedProperties)

Stage 3: mv-expand

| mv-expand todynamic(Entities)

Stage 4: extend

| extend HostName = iff(isnotempty(tostring(Extprop["Compromised Host"])), tolower(tostring(Extprop["Compromised Host"])), tolower(tostring(parse_json(Entities).HostName)))
HostName =
if/* macro: isnotempty(tostring(Extprop["Compromised Host"])) */tolower(tostring(Extprop["Compromised Host"]))
elsetolower(tostring(parse_json(Entities).HostName))

Stage 5: where

| where isnotempty(HostName)

Stage 6: mv-expand

| mv-expand todynamic(split(HostName, ','))

Stage 7: extend (4 consecutive steps)

| extend DnsDomain = iff(isnotempty(tostring(Extprop["Machine Domain"])), tostring(Extprop["Machine Domain"]), tostring(parse_json(Entities).DnsDomain))
| extend UserName = iff(isnotempty(tostring(Extprop["User Name"])), tostring(Extprop["User Name"]), iff(tostring(parse_json(Entities).Type) == 'account', tostring(parse_json(Entities).Name), ''))
| extend NTDomain = iff(isnotempty(tostring(Extprop["User Domain"])), tostring(Extprop["User Domain"]), tostring(parse_json(Entities).NTDomain))
| extend IpAddress = iff(tostring(parse_json(Entities).Type) == 'ip', tostring(parse_json(Entities).Address), tostring(parse_json(Extprop).["IpAddress"]))
DnsDomain =
if/* macro: isnotempty(tostring(Extprop["Machine Domain"])) */tostring(Extprop["Machine Domain"])
elsetostring(parse_json(Entities).DnsDomain)

Stage 8: summarize

| summarize timestamp = arg_max(TimeGenerated, *) by AlertName, tostring(HostName)

Stage 9: project

| project timestamp, AlertName, UserName, NTDomain, tostring(HostName), DnsDomain, IpAddress

Stage 10: join

| join kind=inner
(
CoreAzureBackup
| where State =~ "Deleted"
| where OperationName =~ "BackupItem"
| extend data = split(BackupItemUniqueId, ";")
| extend AzureLocation = data[0], VaultId=data[1], HostName=tolower(tostring(data[2])), DrivesBackedUp=data[3]
| project timestamp = TimeGenerated, AzureLocation, VaultId, HostName, DrivesBackedUp, State, BackupItemUniqueId, _ResourceId, OperationName, BackupItemFriendlyName
)
on HostName

Stage 11: project

| project timestamp, AlertName, HostName, DnsDomain, UserName, NTDomain, _ResourceId, IpAddress, VaultId, AzureLocation, DrivesBackedUp, State, BackupItemUniqueId, OperationName, BackupItemFriendlyName

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.

FieldKindValues
HostNameis_not_null
  • (no value, null check)
OperationNameeq
  • BackupItem
Stateeq
  • Deleted

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.

FieldSource
AlertNameproject
AzureLocationproject
BackupItemFriendlyNameproject
BackupItemUniqueIdproject
DnsDomainproject
DrivesBackedUpproject
HostNameproject
IpAddressproject
NTDomainproject
OperationNameproject
Stateproject
UserNameproject
VaultIdproject
_ResourceIdproject
timestampproject