Detection rules › Kusto
Dataverse - Guest user exfiltration following Power Platform defense impairment
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
| Tactic | Techniques |
|---|---|
| Exfiltration | |
| Defense Evasion |
Telemetry coverage
Rules detecting the same action
These rules filter on the same operation.
- [Entra ID] Authentication Method Changed for Privileged Account (Kusto)
- Account Created and Deleted in Short Timeframe (Kusto)
- Account Created And Deleted Within A Close Time Frame (Sigma)
- Account created from non-approved sources (Kusto)
- Account created or deleted by non-approved user (Kusto)
- Addition of a Temporary Access Pass to a Privileged Account (Kusto)
- Authentication Method Changed for Privileged Account (Kusto)
- Authentication Methods Changed for Privileged Account (Kusto)
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.
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AccountName | project |
AlertDetails | project |
DataverseId | project |
EnvironmentId | project |
GroupRemovalTimestamp | project |
GuestAccountName | project |
GuestUPNSuffix | project |
GuestUser | project |
InstanceUrl | project |
SecurityDisablingUser | project |
TenantIsolationRemovalTimestamp | project |
UPNSuffix | project |