Detection rules › Kusto

Power Platform - DLP policy updated or removed

Status
available
Severity
low
Time window
1d
Group by
EventOriginalUid, TimeGenerated
Source
github.com/Azure/Azure-Sentinel

Identifies changes to DLP policy, specifically policies which are updated or removed.

MITRE ATT&CK coverage

TacticTechniques
StealthT1480 Execution Guardrails

Rule body kusto

id: 1b2e6172-85c5-417a-90c3-7cc80cb787f5
kind: Scheduled
name: Power Platform - DLP policy updated or removed
description: Identifies changes to DLP policy, specifically policies which are updated
  or removed.
severity: Low
status: Available
requiredDataConnectors:
  - connectorId: PowerPlatformAdmin
    dataTypes:
      - PowerPlatformAdminActivity
queryFrequency: 1h
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
relevantTechniques:
  - T1480
query: |
  let create_policy_ignore_time_window = 10m;
  let query_frequency = 1h;
  let dlp_policy_events = PowerPlatformAdminActivity
      | where TimeGenerated >= ago(query_frequency)
      | where EventOriginalType == "GovernanceApiPolicyOperation"
      | where PropertyCollection has_any ("DeleteDlpPolicy", "UpdateDlpPolicy", "CreateDlpPolicy")
      | mv-expand PropertyCollection
      | extend
          Name = tostring(PropertyCollection.Name),
          Value = tostring(PropertyCollection.Value)
      | summarize Properties = make_bag(bag_pack(Name, Value))
          by
          TimeGenerated,
          EventOriginalUid
      | extend
          PolicyName = tostring(Properties['powerplatform.analytics.resource.display_name']),
          EventType = tostring(Properties['powerplatform.analytics.resource.tenant.governance.api_policy.operation_name']),
          ActorName = tostring(Properties['enduser.principal_name']),
          PolicyId = tostring(Properties['powerplatform.analytics.resource.id']),
          AdditionalInfo = Properties['powerplatform.analytics.resource.tenant.governance.api_policy.additional_resources'];
  let delete_events = dlp_policy_events
      | where EventType == "DeleteDlpPolicy";
  let update_events = dlp_policy_events
      | where EventType == "UpdateDlpPolicy";
  let create_events = dlp_policy_events
      | where EventType == "CreateDlpPolicy"
      | extend ignore_time = TimeGenerated + create_policy_ignore_time_window;
  union
      delete_events,
      (update_events
      | join kind=leftouter (
          create_events
          | project-away TimeGenerated
          )
          on PolicyId
      | where isempty(ignore_time) or TimeGenerated > ignore_time
      | project-away ignore_time)
  | where TimeGenerated >= ago(query_frequency)
  | extend
      AccountName = tostring(split(ActorName, "@")[0]),
      UPNSuffix = tostring(split(ActorName, "@")[1])
  | project
      TimeGenerated,
      ActorName,
      EventType,
      PolicyName,
      PolicyId,
      AccountName,
      UPNSuffix,
      AdditionalInfo
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
alertDetailsOverride:
  alertDisplayNameFormat: PowerPlatform - DLP policy {{EventType}} event detected.
  alertDescriptionFormat: A DLP policy {{PolicyName}} was as modfiied or deleted.
    Event type {{EventType}}
customDetails:
  Policy: PolicyId
  PolicyName: PolicyName
version: 3.2.0

Stages and Predicates

Parameters

let create_policy_ignore_time_window = 10m;
let query_frequency = 1h;

Let binding: dlp_policy_events

let dlp_policy_events = PowerPlatformAdminActivity
    | where TimeGenerated >= ago(query_frequency)
    | where EventOriginalType == "GovernanceApiPolicyOperation"
    | where PropertyCollection has_any ("DeleteDlpPolicy", "UpdateDlpPolicy", "CreateDlpPolicy")
    | mv-expand PropertyCollection
    | extend
        Name = tostring(PropertyCollection.Name),
        Value = tostring(PropertyCollection.Value)
    | summarize Properties = make_bag(bag_pack(Name, Value))
        by
        TimeGenerated,
        EventOriginalUid
    | extend
        PolicyName = tostring(Properties['powerplatform.analytics.resource.display_name']),
        EventType = tostring(Properties['powerplatform.analytics.resource.tenant.governance.api_policy.operation_name']),
        ActorName = tostring(Properties['enduser.principal_name']),
        PolicyId = tostring(Properties['powerplatform.analytics.resource.id']),
        AdditionalInfo = Properties['powerplatform.analytics.resource.tenant.governance.api_policy.additional_resources'];

Derived from query_frequency.

Let binding: delete_events

let delete_events = dlp_policy_events
    | where EventType == "DeleteDlpPolicy";

Derived from dlp_policy_events.

Let binding: update_events

let update_events = dlp_policy_events
    | where EventType == "UpdateDlpPolicy";

Derived from dlp_policy_events.

Let binding: create_events

let create_events = dlp_policy_events
    | where EventType == "CreateDlpPolicy"
    | extend ignore_time = TimeGenerated + create_policy_ignore_time_window;

Derived from create_policy_ignore_time_window, dlp_policy_events.

Stage 1: union

union of 2 branches

Stage 2: source

PowerPlatformAdminActivity

Stage 3: where

where ...

Stage 4: where

where EventOriginalType =~ "GovernanceApiPolicyOperation"

Stage 5: where

where (PropertyCollection contains "DeleteDlpPolicy" or PropertyCollection contains "UpdateDlpPolicy" or PropertyCollection contains "CreateDlpPolicy")

Stage 6: mv-expand

mv-expand PropertyCollection

Stage 7: extend

extend Name, Value

Stage 8: summarize

summarize Properties by TimeGenerated, EventOriginalUid

Stage 9: extend

extend ActorName, AdditionalInfo, EventType, PolicyId, PolicyName

Stage 10: where

where EventType =~ "DeleteDlpPolicy"

Stage 11: source

PowerPlatformAdminActivity

Stage 12: where

where ...

Stage 13: where

where EventOriginalType =~ "GovernanceApiPolicyOperation"

Stage 14: where

where (PropertyCollection contains "DeleteDlpPolicy" or PropertyCollection contains "UpdateDlpPolicy" or PropertyCollection contains "CreateDlpPolicy")

Stage 15: mv-expand

mv-expand PropertyCollection

Stage 16: extend

extend Name, Value

Stage 17: summarize

summarize Properties by TimeGenerated, EventOriginalUid

Stage 18: extend

extend ActorName, AdditionalInfo, EventType, PolicyId, PolicyName

Stage 19: where

where EventType =~ "UpdateDlpPolicy"

Stage 20: join

join kind=leftouter (...)

Stage 21: where

where (TimeGenerated > "ignore_time" or isempty(ignore_time))

Stage 22: project-away

project-away ignore_time

Stage 23: where

where ...

Stage 24: extend

extend AccountName, UPNSuffix

Stage 25: project

project AccountName, ActorName, AdditionalInfo, EventType, PolicyId, PolicyName, TimeGenerated, UPNSuffix

Stage 26: summarize

summarize by TimeGenerated, EventOriginalUid

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
EventOriginalTypeeq
  • GovernanceApiPolicyOperation transforms: cased
EventTypeeq
  • CreateDlpPolicy transforms: cased
  • DeleteDlpPolicy transforms: cased
  • UpdateDlpPolicy transforms: cased
PropertyCollectionmatch
  • CreateDlpPolicy
  • DeleteDlpPolicy
  • UpdateDlpPolicy
TimeGeneratedgt
  • ignore_time transforms: cased
ignore_timeis_null
  • (no value, null check)

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
EventOriginalUidsummarize
TimeGeneratedsummarize