Detection rules › Kusto

Dataverse - Guest user exfiltration following Power Platform defense impairment

Status
available
Severity
high
Time window
14d
Group by
EnvironmentId, InstanceUrl, SecurityDisablingUser
Source
github.com/Azure/Azure-Sentinel

Identifies a chain of events starting with disablement of Power Platform tenant isolation and removal of an environment's access security group. These events are correlated with Dataverse exfiltration alerts associated with the impacted environment and recently created Microsoft Entra guest users. Note: Activate other Dataverse analytics rules with the MITRE tactic 'Exfiltration' before enabling this rule.

MITRE ATT&CK coverage

TacticTechniques
ExfiltrationT1567 Exfiltration Over Web Service
Defense EvasionT1629 Impair Defenses

Event coverage

Rule body kusto

id: 39efbf4b-b347-4cc7-895e-99a868bf29ea
kind: Scheduled
name: Dataverse - Guest user exfiltration following Power Platform defense impairment
description: |
  Identifies a chain of events starting with disablement of Power Platform tenant isolation and removal of an environment's access security group. These events are correlated with Dataverse exfiltration alerts associated with the impacted environment and recently created Microsoft Entra guest users.

  Note: Activate other Dataverse analytics rules with the MITRE tactic 'Exfiltration' before enabling this rule.
severity: High
status: Available
requiredDataConnectors:
  - connectorId: PowerPlatformAdmin
    dataTypes:
      - PowerPlatformAdminActivity
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
  - connectorId: AzureActiveDirectoryIdentityProtection
    dataTypes:
      - SecurityAlert
queryFrequency: 1h
queryPeriod: 14d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - DefenseEvasion
  - Exfiltration
relevantTechniques:
  - T1629
  - T1567
query: |
  let query_lookback = 14d;
  let query_frequncy = 1h;
  let defense_evasion_events = PowerPlatformAdminActivity
      | where TimeGenerated >= ago(query_lookback)
      | where EventOriginalType == "TenantIsolationOperation"
      | mv-expand PropertyCollection
      | where PropertyCollection.Name == "powerplatform.analytics.resource.tenant.isolation_policy.enabled"
      | where PropertyCollection.Value == "False"
      | summarize
          TenantIsolationRemovalTimestamp = max(TimeGenerated)
          by SecurityDisablingUser = ActorName
      | join kind=inner (
          PowerPlatformAdminActivity
          | where TimeGenerated >= ago(query_lookback)
          | where EventOriginalType == "EnvironmentPropertyChange"
          | where PropertyCollection has "Property: SecurityGroupId, Old Value: , New Value: "
          | mv-expand PropertyCollection
          | extend
              GroupRemovalTimestamp = TimeGenerated,
              InstanceUrl = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.url", PropertyCollection.Value, "")),
              EnvironmentId = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.name", PropertyCollection.Value, ""))
          | summarize InstanceUrl = max(InstanceUrl), EnvironmentId = max(EnvironmentId) by GroupRemovalTimestamp, SecurityDisablingUser = ActorName)
          on SecurityDisablingUser
      | summarize
          GroupRemovalTimestamp = max(GroupRemovalTimestamp),
          TenantIsolationRemovalTimestamp = max(TenantIsolationRemovalTimestamp)
          by SecurityDisablingUser, InstanceUrl, EnvironmentId;
  let exfiltration_alerts = SecurityAlert
      | where TimeGenerated >= ago(query_frequncy)
      | where Tactics has "Exfiltration"
      | where Entities has ('"AppId":32780')
      | mv-expand todynamic(Entities)
      | extend AlertUPN = iif(Entities.Type == "account", strcat(Entities.Name, "@", Entities.UPNSuffix), "")
      | extend InstanceUrl = tostring(iif(Entities.AppId == 32780, Entities.InstanceName, ""))
      | join kind=inner defense_evasion_events on InstanceUrl
      | where StartTime > TenantIsolationRemovalTimestamp and StartTime > GroupRemovalTimestamp
      | summarize InstanceUrl = max(InstanceUrl), AlertUPN = max(AlertUPN) by AlertName, SystemAlertId
      | extend AlertDetails = bag_pack("AlertName", AlertName, "SystemAlertId", SystemAlertId)
      | summarize AlertDetails = make_set(AlertDetails, 100) by AlertUPN, InstanceUrl
      | join kind=inner (
          AuditLogs
          | where OperationName == "Update user"
          | where Identity == "Microsoft Invitation Acceptance Portal"
          | mv-expand TargetResources
          | extend ModifiedProperties = TargetResources.modifiedProperties
          | mv-expand ModifiedProperties
          | where ModifiedProperties.displayName == "AcceptedAs"
          | summarize RedeemTime = max(TimeGenerated) by GuestUser = tostring(parse_json(replace_regex(tostring(ModifiedProperties.newValue), "\\r", ""))[0]))
          on $left.AlertUPN == $right.GuestUser;
  defense_evasion_events
  | join kind=inner exfiltration_alerts on InstanceUrl
  | extend
      AccountName = tostring(split(SecurityDisablingUser, "@")[0]),
      UPNSuffix = tostring(split(SecurityDisablingUser, "@")[1]),
      GuestAccountName = tostring(split(GuestUser, "@")[0]),
      GuestUPNSuffix = tostring(split(GuestUser, "@")[0]),
      DataverseId = 32780
  | project
      SecurityDisablingUser,
      GuestUser,
      AlertDetails,
      TenantIsolationRemovalTimestamp,
      GroupRemovalTimestamp,
      InstanceUrl,
      EnvironmentId,
      AccountName,
      UPNSuffix,
      GuestAccountName,
      GuestUPNSuffix,
      DataverseId
eventGroupingSettings:
  aggregationKind: SingleAlert
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: AccountName
      - identifier: UPNSuffix
        columnName: UPNSuffix
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: GuestAccountName
      - identifier: UPNSuffix
        columnName: GuestUPNSuffix
  - entityType: CloudApplication
    fieldMappings:
      - identifier: AppId
        columnName: DataverseId
      - identifier: InstanceName
        columnName: InstanceUrl
alertDetailsOverride:
  alertDisplayNameFormat: 'Dataverse - exfiltration alerts following defense impairment
    in {{InstanceUrl}} '
  alertDescriptionFormat: '{{SecurityDisablingUser}} disabled Power Platform tenant
    isolation and removed the security group used to control access to {{{InstanceUrl}}.
    Exfiltration alerts associated with guest users were then detected from user {{{GuestUser}}'
customDetails:
  Environment: EnvironmentId
version: 3.2.0

Stages and Predicates

Parameters

let query_lookback = 14d;
let query_frequncy = 1h;

Let binding: exfiltration_alerts

let exfiltration_alerts = SecurityAlert
    | where TimeGenerated >= ago(query_frequncy)
    | where Tactics has "Exfiltration"
    | where Entities has ('"AppId":32780')
    | mv-expand todynamic(Entities)
    | extend AlertUPN = iif(Entities.Type == "account", strcat(Entities.Name, "@", Entities.UPNSuffix), "")
    | extend InstanceUrl = tostring(iif(Entities.AppId == 32780, Entities.InstanceName, ""))
    | join kind=inner defense_evasion_events on InstanceUrl
    | where StartTime > TenantIsolationRemovalTimestamp and StartTime > GroupRemovalTimestamp
    | summarize InstanceUrl = max(InstanceUrl), AlertUPN = max(AlertUPN) by AlertName, SystemAlertId
    | extend AlertDetails = bag_pack("AlertName", AlertName, "SystemAlertId", SystemAlertId)
    | summarize AlertDetails = make_set(AlertDetails, 100) by AlertUPN, InstanceUrl
    | join kind=inner (
        AuditLogs
        | where OperationName == "Update user"
        | where Identity == "Microsoft Invitation Acceptance Portal"
        | mv-expand TargetResources
        | extend ModifiedProperties = TargetResources.modifiedProperties
        | mv-expand ModifiedProperties
        | where ModifiedProperties.displayName == "AcceptedAs"
        | summarize RedeemTime = max(TimeGenerated) by GuestUser = tostring(parse_json(replace_regex(tostring(ModifiedProperties.newValue), "\\r", ""))[0]))
        on $left.AlertUPN == $right.GuestUser;

Derived from query_frequncy, defense_evasion_events.

The stages below define let defense_evasion_events (the rule's main pipeline source).

Stage 1: source

PowerPlatformAdminActivity

Stage 2: where

| where TimeGenerated >= ago(query_lookback)

Stage 3: where

| where EventOriginalType == "TenantIsolationOperation"

Stage 4: mv-expand

| mv-expand PropertyCollection

Stage 5: where

| where PropertyCollection.Name == "powerplatform.analytics.resource.tenant.isolation_policy.enabled"

Stage 6: where

| where PropertyCollection.Value == "False"

Stage 7: summarize

| summarize
        TenantIsolationRemovalTimestamp = max(TimeGenerated)
        by SecurityDisablingUser = ActorName

Stage 8: join

| join kind=inner (
        PowerPlatformAdminActivity
        | where TimeGenerated >= ago(query_lookback)
        | where EventOriginalType == "EnvironmentPropertyChange"
        | where PropertyCollection has "Property: SecurityGroupId, Old Value: , New Value: "
        | mv-expand PropertyCollection
        | extend
            GroupRemovalTimestamp = TimeGenerated,
            InstanceUrl = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.url", PropertyCollection.Value, "")),
            EnvironmentId = tostring(iif(PropertyCollection.Name == "powerplatform.analytics.resource.environment.name", PropertyCollection.Value, ""))
        | summarize InstanceUrl = max(InstanceUrl), EnvironmentId = max(EnvironmentId) by GroupRemovalTimestamp, SecurityDisablingUser = ActorName)
        on SecurityDisablingUser

Stage 9: summarize

| summarize
        GroupRemovalTimestamp = max(GroupRemovalTimestamp),
        TenantIsolationRemovalTimestamp = max(TenantIsolationRemovalTimestamp)
        by SecurityDisablingUser, InstanceUrl, EnvironmentId

The stages below run on defense_evasion_events (the outer pipeline).

Stage 10: join

defense_evasion_events
| join kind=inner exfiltration_alerts on InstanceUrl

Stage 11: extend

| extend
    AccountName = tostring(split(SecurityDisablingUser, "@")[0]),
    UPNSuffix = tostring(split(SecurityDisablingUser, "@")[1]),
    GuestAccountName = tostring(split(GuestUser, "@")[0]),
    GuestUPNSuffix = tostring(split(GuestUser, "@")[0]),
    DataverseId = 32780

Stage 12: project

| project
    SecurityDisablingUser,
    GuestUser,
    AlertDetails,
    TenantIsolationRemovalTimestamp,
    GroupRemovalTimestamp,
    InstanceUrl,
    EnvironmentId,
    AccountName,
    UPNSuffix,
    GuestAccountName,
    GuestUPNSuffix,
    DataverseId

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
Entitiesmatch
  • "AppId":32780 transforms: term
EventOriginalTypeeq
  • EnvironmentPropertyChange transforms: cased
  • TenantIsolationOperation transforms: cased
Identityeq
  • Microsoft Invitation Acceptance Portal transforms: cased
Nameeq
  • powerplatform.analytics.resource.tenant.isolation_policy.enabled transforms: cased
OperationNameeq
  • Update user transforms: cased
PropertyCollectionmatch
  • Property: SecurityGroupId, Old Value: , New Value: transforms: term
StartTimegt
  • GroupRemovalTimestamp transforms: cased
  • TenantIsolationRemovalTimestamp transforms: cased
Tacticsmatch
  • Exfiltration transforms: term
Valueeq
  • False transforms: cased
displayNameeq
  • AcceptedAs transforms: cased

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
AccountNameproject
AlertDetailsproject
DataverseIdproject
EnvironmentIdproject
GroupRemovalTimestampproject
GuestAccountNameproject
GuestUPNSuffixproject
GuestUserproject
InstanceUrlproject
SecurityDisablingUserproject
TenantIsolationRemovalTimestampproject
UPNSuffixproject