This hunt detects adversaries leveraging compromised privileged identities to bypass security controls by signing in without active Conditional Access protection, indicating a potential gap in identity governance (T1078.004). Proactively hunting for these unprotected sessions is critical because attackers often target high-privilege accounts with weak access policies to establish persistence and move laterally within the Azure environment undetected.
let timeframe = 30d;
let PrivilegedUsers = IdentityInfo
| where isnotempty(AssignedRoles) or isnotempty(PrivilegedEntraPimRoles)
| summarize arg_max(Timestamp, AccountUpn) by AccountObjectId;
SigninLogs
| where TimeGenerated > ago(timeframe)
| where ResultType == "0"
| extend ConditionalAccessStatus = coalesce(ConditionalAccessStatus, "notEvaluated")
| where ConditionalAccessStatus !~ "success"
| join kind=inner PrivilegedUsers on $left.UserId == $right.AccountObjectId
| extend
AccountName = tostring(split(UserPrincipalName, "@")[0]),
AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
| summarize
SignInAttempts = count(),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
ConditionalAccessStatuses = make_set(ConditionalAccessStatus),
AuthRequirements = make_set(AuthenticationRequirement),
LastIP = tostring(arg_max(TimeGenerated, IPAddress))
by UserPrincipalName, AccountName, AccountUPNSuffix, UserId, AccountUpn, AppDisplayName
| project
UserPrincipalName, AccountName, AccountUPNSuffix, AccountUpn,
SignInAttempts, FirstSeen, LastSeen, ConditionalAccessStatuses,
AuthRequirements, LastIP, AppDisplayName
| sort by SignInAttempts desc
id: 87245d60-eefb-42dd-9748-cd1949c83a5e
name: Privileged identities whose sign-ins are not protected by Conditional Access
description: |
Identifies successful sign-ins by accounts with directory or PIM roles when
Conditional Access did not report success, including empty, not applied,
not enabled, or failure states. Review these events for missing policy
coverage and MFA enforcement.
description-detailed: |
Joins privileged accounts from the Microsoft 365 Defender IdentityInfo
table (AssignedRoles / PrivilegedEntraPimRoles) against SigninLogs,
keeping only successful logons whose ConditionalAccessStatus is not
'success' (including empty/null, notApplied, notEnabled, or failure).
Review whether a policy intended to cover privileged roles is
misconfigured, out of scope, or disabled (compare with the
ConditionalAccessPolicyDisabled family of hunting queries in
Hunting Queries/AuditLogs), and prioritize accounts that also
authenticate without MFA (AuthenticationRequirement =
singleFactorAuthentication).
References:
- https://learn.microsoft.com/entra/identity/conditional-access/overview
- https://attack.mitre.org/techniques/T1078/004/
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- IdentityInfo
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
tactics:
- InitialAccess
relevantTechniques:
- T1078.004
query: |
let timeframe = 30d;
let PrivilegedUsers = IdentityInfo
| where isnotempty(AssignedRoles) or isnotempty(PrivilegedEntraPimRoles)
| summarize arg_max(Timestamp, AccountUpn) by AccountObjectId;
SigninLogs
| where TimeGenerated > ago(timeframe)
| where ResultType == "0"
| extend ConditionalAccessStatus = coalesce(ConditionalAccessStatus, "notEvaluated")
| where ConditionalAccessStatus !~ "success"
| join kind=inner PrivilegedUsers on $left.UserId == $right.AccountObjectId
| extend
AccountName = tostring(split(UserPrincipalName, "@")[0]),
AccountUPNSuffix = tostring(split(UserPrincipalName, "@")[1])
| summarize
SignInAttempts = count(),
FirstSeen = min(TimeGenerated),
LastSeen = max(TimeGenerated),
ConditionalAccessStatuses = make_set(ConditionalAccessStatus),
AuthRequirements = make_set(AuthenticationRequirement),
LastIP = tostring(arg_max(TimeGenerated, IPAddress))
by UserPrincipalName, AccountName, AccountUPNSuffix, UserId, AccountUpn, AppDisplayName
| project
UserPrincipalName, AccountName, AccountUPNSuffix, AccountUpn,
SignInAttempts, FirstSeen, LastSeen, ConditionalAccessStatuses,
AuthRequirements, LastIP, AppDisplayName
| sort by SignInAttempts desc
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- identifier: AadUserId
columnName:
| Sentinel Table | Notes |
|---|---|
IdentityInfo | Ensure this data connector is enabled |
SigninLogs | Ensure this data connector is enabled |
Here are 5 specific false positive scenarios for the rule “Privileged identities whose sign-ins are not protected by Conditional Access,” including suggested filters and exclusions:
Scenario: Scheduled Service Account Execution via Azure Automation or Logic Apps
svc-backup, svc-sync) configured to run scheduled jobs within Azure Automation Runbooks or Logic Apps often authenticate using client credentials (App IDs) rather than interactive user flows. These automated workflows frequently bypass standard Conditional Access policies designed for human interaction, resulting in “not applied” states despite being legitimate privileged operations.AuthenticationMethod is ClientSecret or Certificate and the ApplicationId belongs to a known list of automation service principals (e.g., Azure Automation, Logic Apps).Scenario: Legacy On-Premises Hybrid Identity Synchronization
ConditionalAccessStatus is NotApplied and the ApplicationName matches known legacy on-prem applications (e.g., “Azure AD Connect”, “System Center Configuration Manager”) combined with a source IP range restricted to the corporate data center subnet.Scenario: Just-in-Time (JIT) Elevation via PIM for Temporary Access