Detection rules › Kusto

Dataverse - Mass deletion of records

Status
available
Severity
medium
Time window
14d
Group by
ClientIp, EntityName, InstanceUrl, TotalEvents, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies large scale record delete operations based on a predefined threshold and also detects scheduled bulk deletion jobs.

MITRE ATT&CK coverage

TacticTechniques
Impact

Telemetry coverage

Rule body

id: 716cf6d4-97ad-407b-923e-6790083acb58
kind: Scheduled
name: Dataverse - Mass deletion of records
description: Identifies large scale record delete operations based on a predefined
  threshold and also detects scheduled bulk deletion jobs.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1485
query: |
  let mass_delete_threshold = 10000;
  let query_frequency = 1d;
  let delete_activities = DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "Delete";
  union
      (
      delete_activities
      | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl
      | where TotalEvents > mass_delete_threshold
      | join kind=inner (
          delete_activities
          | summarize DeleteCount = count() by UserId, InstanceUrl, ClientIp, EntityName)
          on UserId, InstanceUrl
      | extend Entities = bag_pack("Entity", EntityName, "Count", DeleteCount)
      | summarize Details = make_set(Entities, 100), FirstEvent = min(FirstEvent) by UserId, InstanceUrl, ClientIp, TotalEvents
      ),
      (
      DataverseActivity
      | where TimeGenerated >= ago(query_frequency)
      | where Message == "BulkDelete"
      | summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl, ClientIp
      | extend Details = todynamic("Bulk delete scheduled")
      )
  | extend
      CloudAppId = int(32780),
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1])
  | project
      FirstEvent,
      UserId,
      ClientIp,
      TotalEvents,
      Details,
      InstanceUrl,
      CloudAppId,
      AccountName,
      UPNSuffix
eventGroupingSettings:
  aggregationKind: AlertPerResult
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - mass deletion or bulk deletion job detected
    in {{InstanceUrl}} '
  alertDescriptionFormat: '{{UserId}} triggered the mass deletion detection with the
    following information: {{Details}}'
version: 3.2.0

Stages and Predicates

Stage 0: let

let mass_delete_threshold = 10000;
let query_frequency = 1d;
let delete_activities = DataverseActivity <inlined as stages below>;

Stage 1: union

union of 2 branches

Stage 2: source

DataverseActivity

Stage 3: where

| where TimeGenerated >= ago(query_frequency)

Stage 4: where

| where Message == "Delete"

Stage 5: summarize

| summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl

Stage 6: where

| where TotalEvents > mass_delete_threshold

Stage 7: join

| join kind=inner (
        delete_activities
        | summarize DeleteCount = count() by UserId, InstanceUrl, ClientIp, EntityName)
        on UserId, InstanceUrl

Stage 8: extend

extend Entities

Stage 9: summarize

summarize Details, FirstEvent by UserId, InstanceUrl, ClientIp, TotalEvents

Stage 10: source

DataverseActivity

Stage 11: where

| where TimeGenerated >= ago(query_frequency)

Stage 12: where

| where Message == "BulkDelete"

Stage 13: summarize

| summarize FirstEvent = min(TimeGenerated), TotalEvents = count() by UserId, InstanceUrl, ClientIp

Stage 14: extend

| extend Details = todynamic("Bulk delete scheduled")

Stage 15: extend

extend AccountName, CloudAppId, UPNSuffix

Stage 16: project

project AccountName, ClientIp, CloudAppId, Details, FirstEvent, InstanceUrl, TotalEvents, UPNSuffix, UserId

Stage 17: summarize aggregation inside the join branch

summarize by UserId, InstanceUrl, ClientIp, EntityName

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
ClientIpsummarize
EntityNamesummarize
InstanceUrlsummarize
UserIdsummarize