Detection rules › Kusto

Conditional Access - A Conditional Access user/group/role exclusion has changed

Severity
high
Time window
5m
Source
github.com/Azure/Azure-Sentinel

A Conditional Access user/group/role exclusion has changed in Azure AD.

MITRE ATT&CK coverage

Telemetry coverage

PlatformRecord / event type
AzureUpdate conditional access policy

Rules detecting the same action

These rules filter on the same operation.

Rule body

id: 2ce7f00d-3b3c-41b9-ae9a-b79c19d2394e
name: Conditional Access - A Conditional Access user/group/role exclusion has changed
version: 1.0.1
kind: Scheduled
description: A Conditional Access user/group/role exclusion has changed in Azure AD.
severity: High
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AuditLogs
queryFrequency: 5m
queryPeriod: 5m
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
- DefenseEvasion
- CredentialAccess
relevantTechniques:
- T1098
- T1078
query: |+
  // A Conditional Access user/group/role exclusion has changed.
  AuditLogs
  | where OperationName in ("Update conditional access policy")
  | extend excludeUsersOld = extractjson("$.conditions.users.excludeUsers", tostring(TargetResources[0].modifiedProperties[0].oldValue))
  | extend excludeGroupsOld = extractjson("$.conditions.users.excludeGroups", tostring(TargetResources[0].modifiedProperties[0].oldValue))
  | extend excludeRolesOld = extractjson("$.conditions.users.excludeRoles", tostring(TargetResources[0].modifiedProperties[0].oldValue))
  | extend excludeUsersNew = extractjson("$.conditions.users.excludeUsers", tostring(TargetResources[0].modifiedProperties[0].newValue))
  | extend excludeGroupsNew = extractjson("$.conditions.users.excludeGroups", tostring(TargetResources[0].modifiedProperties[0].newValue))
  | extend excludeRolesNew = extractjson("$.conditions.users.excludeRoles", tostring(TargetResources[0].modifiedProperties[0].newValue))
  | where excludeUsersOld != excludeUsersNew or excludeGroupsOld != excludeGroupsNew or excludeRolesOld != excludeRolesNew
  | extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
  | extend accountName = tostring(split(modifiedBy, "@")[0])
  | extend upnSuffix = tostring(split(modifiedBy, "@")[1])
  | project TimeGenerated, OperationName, policy = TargetResources[0].displayName, modifiedBy, accountName, upnSuffix, result = Result,
            excludeUsersOld, excludeUsersNew, excludeGroupsOld, excludeGroupsNew, excludeRolesOld, excludeRolesNew
  | order by TimeGenerated desc
entityMappings:
- entityType: Account
  fieldMappings:
  - identifier: Name
    columnName: accountName
  - identifier: UPNSuffix
    columnName: upnSuffix
suppressionEnabled: false
suppressionDuration: 5h
eventGroupingSettings:
  aggregationKind: AlertPerResult
incidentConfiguration:
  createIncident: true
  groupingConfiguration:
    enabled: false
    reopenClosedIncident: false
    lookbackDuration: PT1H
    matchingMethod: AllEntities
    groupByEntities: []
    groupByAlertDetails: []
    groupByCustomDetails: []

Stages and Predicates

Stage 1: source

AuditLogs

Stage 2: where

| where OperationName in ("Update conditional access policy")

Stage 3: extend (6 consecutive steps)

| extend excludeUsersOld = extractjson("$.conditions.users.excludeUsers", tostring(TargetResources[0].modifiedProperties[0].oldValue))
| extend excludeGroupsOld = extractjson("$.conditions.users.excludeGroups", tostring(TargetResources[0].modifiedProperties[0].oldValue))
| extend excludeRolesOld = extractjson("$.conditions.users.excludeRoles", tostring(TargetResources[0].modifiedProperties[0].oldValue))
| extend excludeUsersNew = extractjson("$.conditions.users.excludeUsers", tostring(TargetResources[0].modifiedProperties[0].newValue))
| extend excludeGroupsNew = extractjson("$.conditions.users.excludeGroups", tostring(TargetResources[0].modifiedProperties[0].newValue))
| extend excludeRolesNew = extractjson("$.conditions.users.excludeRoles", tostring(TargetResources[0].modifiedProperties[0].newValue))

Stage 4: where

| where excludeUsersOld != excludeUsersNew or excludeGroupsOld != excludeGroupsNew or excludeRolesOld != excludeRolesNew

Stage 5: extend (3 consecutive steps)

| extend modifiedBy = tostring(InitiatedBy.user.userPrincipalName)
| extend accountName = tostring(split(modifiedBy, "@")[0])
| extend upnSuffix = tostring(split(modifiedBy, "@")[1])

Stage 6: project

| project TimeGenerated, OperationName, policy = TargetResources[0].displayName, modifiedBy, accountName, upnSuffix, result = Result,
          excludeUsersOld, excludeUsersNew, excludeGroupsOld, excludeGroupsNew, excludeRolesOld, excludeRolesNew

Stage 7: sort

| order by TimeGenerated desc

Indicators

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
OperationNameproject
TimeGeneratedproject
accountNameproject
excludeGroupsNewproject
excludeGroupsOldproject
excludeRolesNewproject
excludeRolesOldproject
excludeUsersNewproject
excludeUsersOldproject
modifiedByproject
policyproject
resultproject
upnSuffixproject