Detection rules › Kusto

Dataverse - User bulk retrieval outside normal activity

Status
available
Severity
low
Time window
14d
Group by
CurrentExportRate, HistoricalBaseline, InstanceUrl, UserId
Source
github.com/Azure/Azure-Sentinel

Identifies users retrieving significantly more records from Dataverse than they have previously in the past 2 weeks.

MITRE ATT&CK coverage

Telemetry coverage

Rule body

id: 08cb7ffc-59c6-4e7d-88e0-327371c9431b
kind: Scheduled
name: Dataverse - User bulk retrieval outside normal activity
description: Identifies users retrieving significantly more records from Dataverse
  than they have previously in the past 2 weeks.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: Dataverse
    dataTypes:
      - DataverseActivity
queryFrequency: 1d
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
relevantTechniques:
  - T1048
query: |
  let baseline_time = 14d;
  let detection_time = 1d;
  DataverseActivity
  | where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))
  | where Message == "RetrieveMultiple"
  | extend numQueryCount = todouble(QueryResults)
  | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
  | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
  | summarize sum(QueryCount) by UserId
  | extend HistoricalBaseline = sum_QueryCount
  | join kind=inner (
      DataverseActivity
      | where TimeGenerated > ago(detection_time)
      | where Message == "RetrieveMultiple"
      | extend numQueryCount = todouble(QueryResults)
      | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
      | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
      | summarize sum(QueryCount) by UserId
      | extend CurrentExportRate = sum_QueryCount)
      on UserId
  | where CurrentExportRate > HistoricalBaseline
  | project UserId, HistoricalBaseline, CurrentExportRate
  | join kind=inner(
      DataverseActivity
      | where TimeGenerated > ago(detection_time)
      | where Message == "RetrieveMultiple"
      | extend numQueryCount = todouble(QueryResults)
      | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
      | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
      on UserId
  | summarize
      QuerySizes = make_set(QueryCount),
      MostRecentQuery = max(TimeGenerated),
      IPs = make_set(ClientIp),
      UserAgents = make_set(UserAgent),
      Entities = make_set(EntityName),
      Queries = make_set(Query)
      by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate
  | extend
      AccountName = tostring(split(UserId, '@')[0]),
      UPNSuffix = tostring(split(UserId, '@')[1]),
      CloudAppId = int(32780)
  | project
      MostRecentQuery,
      UserId,
      IPs,
      UserAgents,
      InstanceUrl,
      Queries,
      QuerySizes,
      Entities,
      HistoricalBaseline,
      CurrentExportRate,
      AccountName,
      UPNSuffix,
      CloudAppId
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
alertDetailsOverride:
  alertDisplayNameFormat: Dataverse - Bulk record retrieval outside of normal activity
  alertDescriptionFormat: '{{UserId}} exported {{CurrentExportRate}} records, far
    beyond the historical baseline of {{{HistoricalBaseline}}.'
customDetails: {}
version: 3.2.0

Stages and Predicates

Parameters

let baseline_time = 14d;
let detection_time = 1d;

Stage 1: source

DataverseActivity

Stage 2: where

| where TimeGenerated between(ago(baseline_time) .. ago(detection_time - 1d))

Stage 3: where

| where Message == "RetrieveMultiple"

Stage 4: extend (3 consecutive steps)

| extend numQueryCount = todouble(QueryResults)
| extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
| extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))

Stage 5: summarize

| summarize sum(QueryCount) by UserId

Stage 6: extend

| extend HistoricalBaseline = sum_QueryCount

Stage 7: join

| join kind=inner (
    DataverseActivity
    | where TimeGenerated > ago(detection_time)
    | where Message == "RetrieveMultiple"
    | extend numQueryCount = todouble(QueryResults)
    | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
    | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1))
    | summarize sum(QueryCount) by UserId
    | extend CurrentExportRate = sum_QueryCount)
    on UserId

Stage 8: where

| where CurrentExportRate > HistoricalBaseline

Stage 9: project

| project UserId, HistoricalBaseline, CurrentExportRate

Stage 10: join

| join kind=inner(
    DataverseActivity
    | where TimeGenerated > ago(detection_time)
    | where Message == "RetrieveMultiple"
    | extend numQueryCount = todouble(QueryResults)
    | extend QueryCount = iif(QueryResults contains ",", todouble(countof(tostring(QueryResults), ',') + 1), numQueryCount)
    | extend QueryCount = iif(isnotempty(QueryCount), QueryCount, double(1)))
    on UserId

Stage 11: summarize

| summarize
    QuerySizes = make_set(QueryCount),
    MostRecentQuery = max(TimeGenerated),
    IPs = make_set(ClientIp),
    UserAgents = make_set(UserAgent),
    Entities = make_set(EntityName),
    Queries = make_set(Query)
    by UserId, InstanceUrl, HistoricalBaseline, CurrentExportRate

Stage 12: extend

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

Stage 13: project

| project
    MostRecentQuery,
    UserId,
    IPs,
    UserAgents,
    InstanceUrl,
    Queries,
    QuerySizes,
    Entities,
    HistoricalBaseline,
    CurrentExportRate,
    AccountName,
    UPNSuffix,
    CloudAppId

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
CloudAppIdproject
CurrentExportRateproject
Entitiesproject
HistoricalBaselineproject
IPsproject
InstanceUrlproject
MostRecentQueryproject
Queriesproject
QuerySizesproject
UPNSuffixproject
UserAgentsproject
UserIdproject