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

Parameters

let mass_delete_threshold = 10000;
let query_frequency = 1d;

union (2 sources)

Each leg below queries one source; the rule matches if any leg does. Sources: delete_activities, DataverseActivity

Leg 1: delete_activities

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

Leg 2: DataverseActivity

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")

Applied to the combined result

| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])
| project
    FirstEvent,
    UserId,
    ClientIp,
    TotalEvents,
    Details,
    InstanceUrl,
    CloudAppId,
    AccountName,
    UPNSuffix

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
ClientIpsummarize
EntityNamesummarize
InstanceUrlsummarize
UserIdsummarize