Query Details

SigninLogs — Conditional Access Policy Blocked then Successful Bypass

21 SIGNIN Conditional Access Bypass

Query

// ---- Network Allowlist (exclude trusted IPs / CIDR / ranges) --------------
let _allow = materialize(union isfuzzy=true (print R="" | take 0), (_GetWatchlist('NetworkAllowlist') | project R = tostring(IPOrRange)) | where isnotempty(R));
let _allowCIDR  = toscalar(_allow | where not(R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$') | extend R = iff(R has '/', R, strcat(R, '/32')) | summarize make_list(R));
let _allowRange = toscalar(_allow | where R matches regex @'^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$' | summarize make_list(R));
let _ExcludeAllowlistedIPs = (T:(IPAddress:string)) {
    T
    | extend IPAddress = tostring(IPAddress)
    | where array_length(_allowCIDR) == 0 or isnull(ipv4_is_in_any_range(IPAddress, _allowCIDR)) or not(ipv4_is_in_any_range(IPAddress, _allowCIDR))
    | mv-apply _r = _allowRange to typeof(string) on (
        extend _lo = tostring(split(_r,'-')[0]), _hi = tostring(split(_r,'-')[1])
        | extend _inRange = ipv4_compare(IPAddress, _lo) >= 0 and ipv4_compare(IPAddress, _hi) <= 0
        | summarize _anyInRange = max(toint(_inRange)))
    | where isnull(_anyInRange) or _anyInRange == 0
    | project-away _anyInRange
};
// ---------------------------------------------------------------------------
let lookback     = 4h;
let bypassWindow = 4h;
let CABlockedUsers =
    SigninLogs
    | invoke _ExcludeAllowlistedIPs()
    | where TimeGenerated > ago(lookback)
    | where ResultType == "53003"
    | summarize
        BlockedCount = count(),
        LastBlocked  = max(TimeGenerated),
        BlockedIPs   = make_set(IPAddress, 5),
        BlockedApps  = make_set(AppDisplayName, 5)
      by UserPrincipalName;
SigninLogs
| invoke _ExcludeAllowlistedIPs()
| where TimeGenerated > ago(lookback)
| where ResultType == "0"
| join kind=inner CABlockedUsers on UserPrincipalName
| where TimeGenerated > LastBlocked
| where TimeGenerated - LastBlocked <= bypassWindow
| extend MinutesAfterBlock = datetime_diff("minute", TimeGenerated, LastBlocked)
| project
    TimeGenerated, UserPrincipalName,
    IPAddress, Location, AppDisplayName,
    ClientAppUsed, ConditionalAccessStatus,
    RiskLevelDuringSignIn, BlockedCount,
    BlockedIPs, LastBlocked, MinutesAfterBlock

Explanation

This query is designed to detect suspicious sign-in activities in Azure Active Directory. It specifically looks for instances where a user is initially blocked by Conditional Access policies (indicated by a ResultType of 53003) but then successfully signs in within four hours. This could indicate a potential security issue, such as the use of a legacy authentication client that bypasses Conditional Access, an unmanaged device, or a gap in the Conditional Access policy.

Here's a simple breakdown of what the query does:

  1. Network Allowlist: It excludes sign-ins from trusted IP addresses or ranges to focus on potentially suspicious activities.

  2. Lookback Period: The query examines sign-in logs from the past four hours.

  3. Blocked Users: It identifies users who were blocked by Conditional Access policies within the lookback period.

  4. Successful Sign-ins: It then checks if these blocked users managed to sign in successfully within four hours after being blocked.

  5. Alert Generation: If such a scenario is detected, an alert is generated. The alert includes details like the user's name, the number of times they were blocked, and the time it took for them to bypass the block.

  6. Incident Management: The query is configured to create an incident for each detected case, grouping them by user account and IP address for better management.

Overall, this query helps security teams identify and investigate potential security gaps or unauthorized access attempts in their environment.

Details

David Alonso profile picture

David Alonso

Released: April 20, 2026

Tables

SigninLogs

Keywords

SigninLogsAzureActiveDirectoryUserPrincipalNameIPAddressAppDisplayNameClientAppUsedConditionalAccessStatusRiskLevelDuringSignInLocationAccountIP

Operators

letmaterializeunionisfuzzyprinttakeprojectwhereisnotemptytoscalarextendmatchesregexiffstrcatsummarizemake_listnotarray_lengthisnullipv4_is_in_any_rangemv-applytotypeofonsplitipv4_comparemaxtointproject-awayagoinvokeResultTypecountmake_setbyjoinkindinnerdatetime_diff

MITRE Techniques

Actions

GitHub