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
Exfiltration
Defense Evasion

Telemetry coverage

Rules detecting the same action

These rules filter on the same operation.

Rule body

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

Stage 0: let

let query_lookback = 14d;
let query_frequncy = 1h;
let defense_evasion_events = PowerPlatformAdminActivity <inlined as stages below>;
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;

Stage 1: source

PowerPlatformAdminActivity

Stage 2: where

where TimeGenerated >= ago(1209600s)

Stage 3: where

where EventOriginalType =~ "TenantIsolationOperation"

Stage 4: mv-expand

mv-expand PropertyCollection

Stage 5: where

where Name =~ "powerplatform.analytics.resource.tenant.isolation_policy.enabled"

Stage 6: where

where Value =~ False

Stage 7: summarize

summarize SecurityDisablingUser, TenantIsolationRemovalTimestamp by SecurityDisablingUser

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, TenantIsolationRemovalTimestamp by SecurityDisablingUser, InstanceUrl, EnvironmentId

Stage 10: join

join kind=inner (exfiltration_alerts) on InstanceUrl

Stage 11: extend

extend AccountName, DataverseId, GuestAccountName, GuestUPNSuffix, UPNSuffix

Stage 12: project

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

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
Entitiesmatch
  • "AppId":32780 transforms: term
field:"Entities" kind:match
EventOriginalTypeeq
  • EnvironmentPropertyChange
  • TenantIsolationOperation
field:"EventOriginalType" kind:eq
Identityeq
  • Microsoft Invitation Acceptance Portal
field:"Identity" kind:eq value:"Microsoft Invitation Acceptance Portal"
Nameeq
  • powerplatform.analytics.resource.tenant.isolation_policy.enabled
field:"Name" kind:eq value:"powerplatform.analytics.resource.tenant.isolation_policy.enabled"
OperationNameeq
  • Update user
field:"OperationName" kind:eq value:"Update user"
PropertyCollectionmatch
  • Property: SecurityGroupId, Old Value: , New Value: transforms: term
field:"PropertyCollection" kind:match value:"Property: SecurityGroupId, Old Value: , New Value: "
StartTimecross_field_compare
  • GroupRemovalTimestamp transforms: op:gt
  • TenantIsolationRemovalTimestamp transforms: op:gt
field:"StartTime" kind:cross_field_compare
Tacticsmatch
  • Exfiltration transforms: term
field:"Tactics" kind:match value:"Exfiltration"
Valueeq
  • False
field:"Value" kind:eq value:"False"
displayNameeq
  • AcceptedAs
field:"displayName" kind:eq value:"AcceptedAs"

Output fields

These fields are emitted when the rule matches.

FieldSource
AccountNameproject
AlertDetailsproject
DataverseIdproject
EnvironmentIdproject
GroupRemovalTimestampproject
GuestAccountNameproject
GuestUPNSuffixproject
GuestUserproject
InstanceUrlproject
SecurityDisablingUserproject
TenantIsolationRemovalTimestampproject
UPNSuffixproject