Detection rules › Kusto
Detect suspicious conditional access policy modifications
This detection rule flags events where conditional access policies are getting weaker, when modifications to CA inclusion or exclusion groups are happening, or when the effectiveness of a policy is disabled.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | |
| Defense Impairment | |
| Credential Access |
References
Telemetry coverage
Rules detecting the same action
These rules filter on the same operation.
- Azure MFA Disabled (Panther)
- Azure Policy Violation Detected (Panther)
- CA Policy Removed by Non Approved Actor (Sigma)
- CA Policy Updated by Non Approved Actor (Sigma)
- Certificate-Based Authentication Enabled (Sigma)
- Changes to Device Registration Policy (Sigma)
- Conditional Access - A Conditional Access app exclusion has changed (Kusto)
- Conditional Access - A Conditional Access Device platforms condition has changed (the Device platforms condition can be spoofed) (Kusto)
Rule body
// !! TO DO: CHANGE TO YOUR CA GROUP NAMING CONVENTION !!
let ca_include_naming_convention = "CA-Include";
let ca_exclude_naming_convention = "CA-Exclude";
// OPTIONAL - Get PIM activations with justifications for CA changes
let ca_pim_activations = AuditLogs
// Get PIM activations
| where TimeGenerated > ago(24h)
| where OperationName contains "completed (PIM activation)"
// Parse details
| parse AdditionalDetails with * "{\"key\":\"StartTime\",\"value\":\"" PimStartTime "\"" * "{\"key\":\"ExpirationTime\",\"value\":\"" PimExpirationTime "\"" * "{\"key\":\"Justification\",\"value\":\"" PimJustification "\"" *
// Only get CA related PIM justifications
| where PimJustification has_any ("Conditional Access", "CA", "Trusted", "Named", "Location")
// Extend and projects
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| project OperationName, PimJustification, PimStartTime, PimExpirationTime, UserPrincipalName;
// Get suspicious policy changes
let policy_changes = AuditLogs
// Get CA updates
| where TimeGenerated > ago(24h)
| where OperationName in ("Update conditional access policy", "Delete conditional access policy")
// Expand Target resources and the modified properties
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
// Save the new and old values
| extend NewValueConditions = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).conditions
| extend OldValueConditions = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).conditions
| extend NewValueGrandControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).grantControls
| extend OldValueGrandControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).grantControls
| extend NewValueSessionControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).sessionControls
| extend OldValueSessionControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).sessionControls
| extend NewState = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).state
| extend OldState = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).state
// Count the new inlude arrays
| extend CountNewUserIncludes = array_length(NewValueConditions.users.includeUsers),
CountNewRoleIncludes = array_length(NewValueConditions.users.includeRoles),
CountNewGroupIncludes = array_length(NewValueConditions.users.includeGroups),
CountNewUserActionIncludes = array_length(NewValueConditions.applications.inlcudeUserActions),
CountNewAuthContextIncludes = array_length(NewValueConditions.applications.includeAuthenticationContextClassReferences),
CountNewApplicationIncludes = array_length(NewValueConditions.applications.inlcudeApplications),
CountNewLocationIncludes = array_length(NewValueConditions.locations.includeLocations),
CountNewPlatformIncludes = array_length(NewValueConditions.platforms.includePlatforms)
// Count the old inlude arrays
| extend CountOldUserIncludes = array_length(OldValueConditions.users.includeUsers),
CountOldRoleIncludes = array_length(OldValueConditions.users.includeRoles),
CountOldGroupIncludes = array_length(OldValueConditions.users.includeGroups),
CountOldUserActionIncludes = array_length(OldValueConditions.applications.inlcudeUserActions),
CountOldAuthContextIncludes = array_length(OldValueConditions.applications.includeAuthenticationContextClassReferences),
CountOldApplicationIncludes = array_length(OldValueConditions.applications.inlcudeApplications),
CountOldLocationIncludes = array_length(OldValueConditions.locations.includeLocations),
CountOldPlatformIncludes = array_length(OldValueConditions.platforms.includePlatforms)
// Count the new exclude arrays
| extend CountNewUserExcludes = array_length(NewValueConditions.users.excludeUsers),
CountNewRoleExcludes = array_length(NewValueConditions.users.excludeRoles),
CountNewGroupExcludes = array_length(NewValueConditions.users.excludeGroups),
CountNewApplicationExcludes = array_length(NewValueConditions.applications.excludeApplications),
CountNewLocationExcludes = array_length(NewValueConditions.locations.excludeLocations),
CountNewPlatformExcludes = array_length(NewValueConditions.platforms.excludePlatforms)
// Count the old exclude arrays
| extend CountOldUserExcludes = array_length(OldValueConditions.users.excludeUsers),
CountOldRoleExcludes = array_length(OldValueConditions.users.excludeRoles),
CountOldGroupExcludes = array_length(OldValueConditions.users.excludeGroups),
CountOldApplicationExcludes = array_length(OldValueConditions.applications.excludeApplications),
CountOldLocationExcludes = array_length(OldValueConditions.locations.excludeLocations),
CountOldPlatformExcludes = array_length(OldValueConditions.platforms.excludePlatforms)
// Alert when includes are taken away and excludes are added, application filter changes, or AppType changes
| extend Reasons = dynamic([])
| extend Reasons = iff(CountNewUserIncludes < CountOldUserIncludes, array_concat(Reasons, dynamic(["User removed from include"])), Reasons)
| extend Reasons = iff(CountNewRoleIncludes < CountOldRoleIncludes, array_concat(Reasons, dynamic(["Role removed from include"])), Reasons)
| extend Reasons = iff(CountNewGroupIncludes < CountOldGroupIncludes, array_concat(Reasons, dynamic(["Group removed from include"])), Reasons)
| extend Reasons = iff(CountNewUserExcludes > CountOldUserExcludes, array_concat(Reasons, dynamic(["User added to exclude"])), Reasons)
| extend Reasons = iff(CountNewRoleExcludes > CountOldRoleExcludes, array_concat(Reasons, dynamic(["Role added to exclude"])), Reasons)
| extend Reasons = iff(CountNewGroupExcludes > CountOldGroupExcludes, array_concat(Reasons, dynamic(["Group added to exclude"])), Reasons)
| extend Reasons = iff(CountNewUserActionIncludes < CountOldUserActionIncludes, array_concat(Reasons, dynamic(["User action removed from include"])), Reasons)
| extend Reasons = iff(CountNewAuthContextIncludes < CountOldAuthContextIncludes, array_concat(Reasons, dynamic(["Authentication context removed from include"])), Reasons)
| extend Reasons = iff(CountNewApplicationIncludes < CountOldApplicationIncludes, array_concat(Reasons, dynamic(["Application removed from include"])), Reasons)
| extend Reasons = iff(CountNewApplicationExcludes > CountOldApplicationExcludes, array_concat(Reasons, dynamic(["Application added to exclude"])), Reasons)
| extend Reasons = iff(CountNewLocationIncludes < CountOldLocationIncludes, array_concat(Reasons, dynamic(["Locations removed from include"])), Reasons)
| extend Reasons = iff(CountNewLocationExcludes > CountOldLocationExcludes, array_concat(Reasons, dynamic(["Locations added to exclude"])), Reasons)
| extend Reasons = iff(CountNewPlatformIncludes < CountOldPlatformIncludes, array_concat(Reasons, dynamic(["Platforms removed from include"])), Reasons)
| extend Reasons = iff(CountNewPlatformExcludes > CountOldPlatformExcludes, array_concat(Reasons, dynamic(["Platforms added to exclude"])), Reasons)
// Flag general changes
| extend Reasons = iff(tostring(NewValueConditions.applications.applicationFilter) != tostring(OldValueConditions.applications.applicationFilter), array_concat(Reasons, dynamic(["Application filter changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.clientAppTypes) != tostring(OldValueConditions.clientAppTypes), array_concat(Reasons, dynamic(["Client app type changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.userRiskLevels) != tostring(OldValueConditions.userRiskLevels), array_concat(Reasons, dynamic(["User risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.signInRiskLevels) != tostring(OldValueConditions.signInRiskLevels), array_concat(Reasons, dynamic(["Sign-in risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.servicePrincipalRiskLevels) != tostring(OldValueConditions.servicePrincipalRiskLevels), array_concat(Reasons, dynamic(["Service Principal risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueGrandControls) != tostring(OldValueGrandControls), array_concat(Reasons, dynamic(["Grant controls changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueSessionControls) != tostring(OldValueSessionControls), array_concat(Reasons, dynamic(["Session controls changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.devices) != tostring(OldValueConditions.devices), array_concat(Reasons, dynamic(["Device conditions changed"])), Reasons)
// Flag Change from include 'all' to only include specifics (since this can evade the count detections)
| extend Reasons = iff(tostring(OldValueConditions.locations.includeLocations) contains "all" and tostring(NewValueConditions.locations.includeLocations) !contains "all", array_concat(Reasons, dynamic(["Include locations changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.platforms.includePlatforms) contains "all" and tostring(NewValueConditions.platforms.includePlatforms) !contains "all", array_concat(Reasons, dynamic(["Include platforms changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.users.includeUsers) contains "all" and tostring(NewValueConditions.users.includeUsers) !contains "all", array_concat(Reasons, dynamic(["Include users changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.applications.includeApplications) contains "all" and tostring(NewValueConditions.applications.includeApplications) !contains "all", array_concat(Reasons, dynamic(["Include applications changed from all to specific"])), Reasons)
// Flag state change to inactive
| extend Reasons = iff(tostring(OldState) == "enabled" and tostring(NewState) != "enabled", array_concat(Reasons, dynamic(["Policy was disabled"])), Reasons)
// Flag policy deletion
| extend Reasons = iff(OperationName == "Delete conditional access policy", array_concat(Reasons, dynamic(["Policy was deleted"])), Reasons);
// Get trusted named location changes
let named_locations = AuditLogs
// Get named location changes
| where TimeGenerated > ago(24h)
| where OperationName in ("Add named location", "Update named location")
// Expand Target resources and the modified properties
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
// Always flag when the named location is trusted
| extend NewValueIsTrusted = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).isTrusted
| where NewValueIsTrusted == "true"
// Add reason
| extend Reasons = dynamic([])
| extend Reasons = iff(OperationName == "Add named location", array_concat(Reasons, dynamic(["Trusted named location was added"])), Reasons)
| extend Reasons = iff(OperationName == "Update named location", array_concat(Reasons, dynamic(["Trusted named location was updated"])), Reasons);
// Get changes to groups used in CA policies
let remove_from_include_group = AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Remove member from group"
// Expand Target resources and the modified properties
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
// Search for the display name of the edited group and find groups with CA naming convention
| where TargetResources_modifiedProperties.displayName == "Group.DisplayName" and TargetResources_modifiedProperties contains ca_include_naming_convention
// Add reason
| extend Reasons = dynamic([])
| extend Reasons = dynamic(["Member removed from include group used in CA policy"]);
let add_to_exclude_group = AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName == "Add member to group"
// Expand Target resources and the modified properties
| mv-expand TargetResources
| mv-expand TargetResources.modifiedProperties
// Search for the display name of the edited group and find groups with CA naming convention
| where TargetResources_modifiedProperties.displayName == "Group.DisplayName" and TargetResources_modifiedProperties contains ca_exclude_naming_convention
// Add reason
| extend Reasons = dynamic([])
| extend Reasons = dynamic(["Member added to exclude group used in CA policy"]);
// Union all detections
union policy_changes, named_locations, remove_from_include_group, add_to_exclude_group
// Check if reason array is empty
| where Reasons != "[]"
// Sorting and project
| sort by TimeGenerated desc
| project TimeGenerated, OperationName, InitiatedBy, LoggedByService, Result, TargetResources, AADOperationType, Reasons
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
// Look for PIM activations from the same user who performed changes
| join kind=leftouter ca_pim_activations on UserPrincipalName
| project-away UserPrincipalName1
// Check if PIM was justified for user, and only show non-justified PIMs
| extend JustifiedPIM = iff(isnotempty(PimStartTime) and isnotempty(PimExpirationTime) and TimeGenerated between (todatetime(PimStartTime) .. todatetime(PimExpirationTime)), true, false)
| where JustifiedPIM == false
Stages and Predicates
Stage 0: let
let ca_include_naming_convention = "CA-Include";
let ca_exclude_naming_convention = "CA-Exclude";
let ca_pim_activations = AuditLogs
| where TimeGenerated > ago(24h)
| where OperationName contains "completed (PIM activation)"
| parse AdditionalDetails with * "{\"key\":\"StartTime\",\"value\":\"" PimStartTime "\"" * "{\"key\":\"ExpirationTime\",\"value\":\"" PimExpirationTime "\"" * "{\"key\":\"Justification\",\"value\":\"" PimJustification "\"" *
| where PimJustification has_any ("Conditional Access", "CA", "Trusted", "Named", "Location")
| extend UserPrincipalName = tostring(InitiatedBy.user.userPrincipalName)
| project OperationName, PimJustification, PimStartTime, PimExpirationTime, UserPrincipalName;
let policy_changes = AuditLogs <inlined as stages below>;
let named_locations = AuditLogs <inlined as stages below>;
let remove_from_include_group = AuditLogs <inlined as stages below>;
let add_to_exclude_group = AuditLogs <inlined as stages below>;
Stage 1: union
union of 4 branches
Stage 2: source
AuditLogs
Stage 3: where
| where TimeGenerated > ago(24h)
Stage 4: where
| where OperationName in ("Update conditional access policy", "Delete conditional access policy")
Stage 5: mv-expand
| mv-expand TargetResources
Stage 6: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 7: extend (41 consecutive steps)
| extend NewValueConditions = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).conditions
| extend OldValueConditions = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).conditions
| extend NewValueGrandControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).grantControls
| extend OldValueGrandControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).grantControls
| extend NewValueSessionControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).sessionControls
| extend OldValueSessionControls = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).sessionControls
| extend NewState = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).state
| extend OldState = parse_json(tostring(parse_json(TargetResources_modifiedProperties.oldValue))).state
| extend CountNewUserIncludes = array_length(NewValueConditions.users.includeUsers),
CountNewRoleIncludes = array_length(NewValueConditions.users.includeRoles),
CountNewGroupIncludes = array_length(NewValueConditions.users.includeGroups),
CountNewUserActionIncludes = array_length(NewValueConditions.applications.inlcudeUserActions),
CountNewAuthContextIncludes = array_length(NewValueConditions.applications.includeAuthenticationContextClassReferences),
CountNewApplicationIncludes = array_length(NewValueConditions.applications.inlcudeApplications),
CountNewLocationIncludes = array_length(NewValueConditions.locations.includeLocations),
CountNewPlatformIncludes = array_length(NewValueConditions.platforms.includePlatforms)
| extend CountOldUserIncludes = array_length(OldValueConditions.users.includeUsers),
CountOldRoleIncludes = array_length(OldValueConditions.users.includeRoles),
CountOldGroupIncludes = array_length(OldValueConditions.users.includeGroups),
CountOldUserActionIncludes = array_length(OldValueConditions.applications.inlcudeUserActions),
CountOldAuthContextIncludes = array_length(OldValueConditions.applications.includeAuthenticationContextClassReferences),
CountOldApplicationIncludes = array_length(OldValueConditions.applications.inlcudeApplications),
CountOldLocationIncludes = array_length(OldValueConditions.locations.includeLocations),
CountOldPlatformIncludes = array_length(OldValueConditions.platforms.includePlatforms)
| extend CountNewUserExcludes = array_length(NewValueConditions.users.excludeUsers),
CountNewRoleExcludes = array_length(NewValueConditions.users.excludeRoles),
CountNewGroupExcludes = array_length(NewValueConditions.users.excludeGroups),
CountNewApplicationExcludes = array_length(NewValueConditions.applications.excludeApplications),
CountNewLocationExcludes = array_length(NewValueConditions.locations.excludeLocations),
CountNewPlatformExcludes = array_length(NewValueConditions.platforms.excludePlatforms)
| extend CountOldUserExcludes = array_length(OldValueConditions.users.excludeUsers),
CountOldRoleExcludes = array_length(OldValueConditions.users.excludeRoles),
CountOldGroupExcludes = array_length(OldValueConditions.users.excludeGroups),
CountOldApplicationExcludes = array_length(OldValueConditions.applications.excludeApplications),
CountOldLocationExcludes = array_length(OldValueConditions.locations.excludeLocations),
CountOldPlatformExcludes = array_length(OldValueConditions.platforms.excludePlatforms)
| extend Reasons = dynamic([])
| extend Reasons = iff(CountNewUserIncludes < CountOldUserIncludes, array_concat(Reasons, dynamic(["User removed from include"])), Reasons)
| extend Reasons = iff(CountNewRoleIncludes < CountOldRoleIncludes, array_concat(Reasons, dynamic(["Role removed from include"])), Reasons)
| extend Reasons = iff(CountNewGroupIncludes < CountOldGroupIncludes, array_concat(Reasons, dynamic(["Group removed from include"])), Reasons)
| extend Reasons = iff(CountNewUserExcludes > CountOldUserExcludes, array_concat(Reasons, dynamic(["User added to exclude"])), Reasons)
| extend Reasons = iff(CountNewRoleExcludes > CountOldRoleExcludes, array_concat(Reasons, dynamic(["Role added to exclude"])), Reasons)
| extend Reasons = iff(CountNewGroupExcludes > CountOldGroupExcludes, array_concat(Reasons, dynamic(["Group added to exclude"])), Reasons)
| extend Reasons = iff(CountNewUserActionIncludes < CountOldUserActionIncludes, array_concat(Reasons, dynamic(["User action removed from include"])), Reasons)
| extend Reasons = iff(CountNewAuthContextIncludes < CountOldAuthContextIncludes, array_concat(Reasons, dynamic(["Authentication context removed from include"])), Reasons)
| extend Reasons = iff(CountNewApplicationIncludes < CountOldApplicationIncludes, array_concat(Reasons, dynamic(["Application removed from include"])), Reasons)
| extend Reasons = iff(CountNewApplicationExcludes > CountOldApplicationExcludes, array_concat(Reasons, dynamic(["Application added to exclude"])), Reasons)
| extend Reasons = iff(CountNewLocationIncludes < CountOldLocationIncludes, array_concat(Reasons, dynamic(["Locations removed from include"])), Reasons)
| extend Reasons = iff(CountNewLocationExcludes > CountOldLocationExcludes, array_concat(Reasons, dynamic(["Locations added to exclude"])), Reasons)
| extend Reasons = iff(CountNewPlatformIncludes < CountOldPlatformIncludes, array_concat(Reasons, dynamic(["Platforms removed from include"])), Reasons)
| extend Reasons = iff(CountNewPlatformExcludes > CountOldPlatformExcludes, array_concat(Reasons, dynamic(["Platforms added to exclude"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.applications.applicationFilter) != tostring(OldValueConditions.applications.applicationFilter), array_concat(Reasons, dynamic(["Application filter changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.clientAppTypes) != tostring(OldValueConditions.clientAppTypes), array_concat(Reasons, dynamic(["Client app type changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.userRiskLevels) != tostring(OldValueConditions.userRiskLevels), array_concat(Reasons, dynamic(["User risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.signInRiskLevels) != tostring(OldValueConditions.signInRiskLevels), array_concat(Reasons, dynamic(["Sign-in risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.servicePrincipalRiskLevels) != tostring(OldValueConditions.servicePrincipalRiskLevels), array_concat(Reasons, dynamic(["Service Principal risk levels changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueGrandControls) != tostring(OldValueGrandControls), array_concat(Reasons, dynamic(["Grant controls changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueSessionControls) != tostring(OldValueSessionControls), array_concat(Reasons, dynamic(["Session controls changed"])), Reasons)
| extend Reasons = iff(tostring(NewValueConditions.devices) != tostring(OldValueConditions.devices), array_concat(Reasons, dynamic(["Device conditions changed"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.locations.includeLocations) contains "all" and tostring(NewValueConditions.locations.includeLocations) !contains "all", array_concat(Reasons, dynamic(["Include locations changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.platforms.includePlatforms) contains "all" and tostring(NewValueConditions.platforms.includePlatforms) !contains "all", array_concat(Reasons, dynamic(["Include platforms changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.users.includeUsers) contains "all" and tostring(NewValueConditions.users.includeUsers) !contains "all", array_concat(Reasons, dynamic(["Include users changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldValueConditions.applications.includeApplications) contains "all" and tostring(NewValueConditions.applications.includeApplications) !contains "all", array_concat(Reasons, dynamic(["Include applications changed from all to specific"])), Reasons)
| extend Reasons = iff(tostring(OldState) == "enabled" and tostring(NewState) != "enabled", array_concat(Reasons, dynamic(["Policy was disabled"])), Reasons)
| extend Reasons = iff(OperationName == "Delete conditional access policy", array_concat(Reasons, dynamic(["Policy was deleted"])), Reasons)
Stage 8: source
AuditLogs
Stage 9: where
| where TimeGenerated > ago(24h)
Stage 10: where
| where OperationName in ("Add named location", "Update named location")
Stage 11: mv-expand
| mv-expand TargetResources
Stage 12: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 13: extend
| extend NewValueIsTrusted = parse_json(tostring(parse_json(TargetResources_modifiedProperties.newValue))).isTrusted
Stage 14: where
| where NewValueIsTrusted == "true"
Stage 15: extend (3 consecutive steps)
| extend Reasons = dynamic([])
| extend Reasons = iff(OperationName == "Add named location", array_concat(Reasons, dynamic(["Trusted named location was added"])), Reasons)
| extend Reasons = iff(OperationName == "Update named location", array_concat(Reasons, dynamic(["Trusted named location was updated"])), Reasons)
Stage 16: source
AuditLogs
Stage 17: where
| where TimeGenerated > ago(24h)
Stage 18: where
| where OperationName == "Remove member from group"
Stage 19: mv-expand
| mv-expand TargetResources
Stage 20: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 21: where
| where TargetResources_modifiedProperties.displayName == "Group.DisplayName" and TargetResources_modifiedProperties contains ca_include_naming_convention
Stage 22: extend
| extend Reasons = dynamic([])
Stage 23: extend
| extend Reasons = dynamic(["Member removed from include group used in CA policy"])
Stage 24: source
AuditLogs
Stage 25: where
| where TimeGenerated > ago(24h)
Stage 26: where
| where OperationName == "Add member to group"
Stage 27: mv-expand
| mv-expand TargetResources
Stage 28: mv-expand
| mv-expand TargetResources.modifiedProperties
Stage 29: where
| where TargetResources_modifiedProperties.displayName == "Group.DisplayName" and TargetResources_modifiedProperties contains ca_exclude_naming_convention
Stage 30: extend
| extend Reasons = dynamic([])
Stage 31: extend
| extend Reasons = dynamic(["Member added to exclude group used in CA policy"])
Stage 32: where
where Reasons !~ "[]"
Stage 33: sort
sort by TimeGenerated
Stage 34: project
project AADOperationType, InitiatedBy, LoggedByService, OperationName, Reasons, Result, TargetResources, TimeGenerated
Stage 35: extend
extend UserPrincipalName
Stage 36: join
| join kind=leftouter ca_pim_activations on UserPrincipalName
Stage 37: project-away
project-away UserPrincipalName1
Stage 38: extend
extend JustifiedPIM
JustifiedPIM =if
(isnotempty(PimStartTime) and isnotempty(PimExpirationTime))trueelse
falseStage 39: where
where JustifiedPIM =~ false
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
JustifiedPIM | eq |
| field:"JustifiedPIM" kind:eq value:"false" |
NewValueIsTrusted | eq |
| field:"NewValueIsTrusted" kind:eq value:"true" |
OperationName | contains |
| field:"OperationName" kind:contains value:"completed (PIM activation)" |
OperationName | eq |
| field:"OperationName" kind:eq |
OperationName | in |
| field:"OperationName" kind:in |
PimJustification | match |
| field:"PimJustification" kind:match |
Reasons | ne |
| field:"Reasons" kind:ne value:"[]" |
TargetResources_modifiedProperties | contains |
| field:"TargetResources_modifiedProperties" kind:contains |
displayName | eq |
| field:"displayName" kind:eq value:"Group.DisplayName" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
AADOperationType | project |
InitiatedBy | project |
LoggedByService | project |
OperationName | project |
Reasons | project |
Result | project |
TargetResources | project |
TimeGenerated | project |
UserPrincipalName | extend |
JustifiedPIM | extend |