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

Parameters

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

let defense_evasion_events is inlined into the numbered stages below.

Let binding: exfiltration_alerts used in Stage 10

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;

Stages 1 to 9 define let defense_evasion_events (the rule's main pipeline source); stages 10 to 12 run on it.

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

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

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