Detection rules › Kusto

Dataverse - Mass record updates

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

This query detects mass record update changes in Dataverse and Dynamics 365, exceeding a pre-defined threshold.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: df577f0f-1d8a-4420-9057-a07f0edb15c8
kind: Scheduled
name: Dataverse - Mass record updates
description: This query detects mass record update changes in Dataverse and Dynamics
  365, exceeding a pre-defined threshold.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Impact
relevantTechniques:
  - T1641
  - T1485
  - T1565
query: |
  // Set threshold for number of updated records
  let detection_threshold = 10000;
  let query_frequency = 1h;
  DataverseActivity
  | where TimeGenerated >= ago(query_frequency)
  | where Message == "Update"
  | summarize EventCount = count() by InstanceUrl, UserId, ClientIp, Message
  | where EventCount > detection_threshold
  | join kind=inner(
      DataverseActivity
      | where TimeGenerated >= ago(query_frequency))
      on InstanceUrl, UserId, ClientIp, Message
  | mv-expand Fields
  | summarize
      UpdatedFields = make_set(Fields.Name, 100),
      FirstEvent = min(TimeGenerated)
      by UserId, ClientIp, InstanceUrl, EventCount, EntityName
  | extend Details = bag_pack("Entity", EntityName, "Count", EventCount, "FieldsUpdated", UpdatedFields)
  | summarize
      TotalEvents = sum(EventCount),
      FirstEvent = min(FirstEvent),
      Details = make_list(Details, 100)
      by UserId, ClientIp, InstanceUrl
  | 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: IP
    fieldMappings:
      - identifier: Address
        columnName: ClientIp
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: CloudAppId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - Mass record changes detected in {{{InstanceUrl}} '
  alertDescriptionFormat: A total of {{TotalEvents}} records were updated by {{UserId}}
    , breaching the mass update threshold in {{InstanceUrl}} .
customDetails:
  Details: Details
version: 3.2.0

Stages and Predicates

Parameters

let detection_threshold = 10000;
let query_frequency = 1h;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated >= ago(query_frequency)

Stage 3: where

| where Message == "Update"

Stage 4: summarize

| summarize EventCount = count() by InstanceUrl, UserId, ClientIp, Message

Stage 5: where

| where EventCount > detection_threshold

Stage 6: join

| join kind=inner(
    DataverseActivity
    | where TimeGenerated >= ago(query_frequency))
    on InstanceUrl, UserId, ClientIp, Message

Stage 7: mv-expand

| mv-expand Fields

Stage 8: summarize

| summarize
    UpdatedFields = make_set(Fields.Name, 100),
    FirstEvent = min(TimeGenerated)
    by UserId, ClientIp, InstanceUrl, EventCount, EntityName

Stage 9: extend

| extend Details = bag_pack("Entity", EntityName, "Count", EventCount, "FieldsUpdated", UpdatedFields)

Stage 10: summarize

| summarize
    TotalEvents = sum(EventCount),
    FirstEvent = min(FirstEvent),
    Details = make_list(Details, 100)
    by UserId, ClientIp, InstanceUrl

Stage 11: extend

| extend
    CloudAppId = int(32780),
    AccountName = tostring(split(UserId, '@')[0]),
    UPNSuffix = tostring(split(UserId, '@')[1])

Stage 12: project

| 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
AccountNameproject
ClientIpproject
CloudAppIdproject
Detailsproject
FirstEventproject
InstanceUrlproject
TotalEventsproject
UPNSuffixproject
UserIdproject