Query Details
// Rule : Workload Identity - SP Conditional Access Bypass → Cloud App Events Correlation
// Severity: High
// Tactics : InitialAccess, Exfiltration
// MITRE : T1078.004, T1567, T1530
// Freq : PT1H Period: PT2H
//==========================================================================================
// ---- 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 PrivateRanges = dynamic(["10.", "192.168.", "172.16.", "172.17.", "172.18.",
"172.19.", "172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.",
"172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31.",
"127.", "169.254.", "168.63."]);
let HighRiskActions = dynamic([
"FileDownloaded", "FileSyncDownloadedFull", "FileDeleted", "FileMoved",
"SendAs", "MailItemsAccessed", "MassDelete", "DataExfiltration"
]);
// --- SPs that bypassed CA in the last 2 hours ---
let CABypassSPs = (AADServicePrincipalSignInLogs | invoke _ExcludeAllowlistedIPs())
| where TimeGenerated > ago(2h)
| where ResultType == "0"
| where ConditionalAccessStatus in ("notApplied", "failure")
| where isnotempty(IPAddress)
| where not(IPAddress has_any (PrivateRanges))
| project SigninTime = TimeGenerated, ServicePrincipalId, ServicePrincipalName,
AppId, SigninIP = IPAddress, CAStatus = ConditionalAccessStatus,
Resource = ResourceDisplayName;
// --- Subsequent high-risk cloud app activity from same SP within 1h ---
// union isfuzzy=true + datatable fallback: deployment succeeds even if CloudAppEvents (M365D connector) is not enabled
union isfuzzy=true
(CloudAppEvents | invoke _ExcludeAllowlistedIPs()),
(datatable(TimeGenerated:datetime, ActionType:string, AccountObjectId:string, ObjectName:string, Application:string)[])
| where TimeGenerated > ago(2h)
| where ActionType in (HighRiskActions)
| join kind=inner CABypassSPs on $left.AccountObjectId == $right.ServicePrincipalId
| where TimeGenerated > SigninTime and TimeGenerated <= SigninTime + 1h
| summarize
ActionCount = count(),
Actions = make_set(ActionType, 10),
AffectedObjects = make_set(ObjectName, 20),
Applications = make_set(Application, 5),
SigninIP = any(SigninIP),
CAStatus = any(CAStatus),
Resource = any(Resource),
SigninTime = any(SigninTime),
FirstAction = min(TimeGenerated),
LastAction = max(TimeGenerated)
by ServicePrincipalName, ServicePrincipalId, AppId
| where ActionCount >= 2
This KQL query is designed to detect potentially malicious activity involving service principals (SPs) in a cloud environment. Here's a simplified breakdown of what the query does:
Network Allowlist: It defines a list of trusted IP addresses and ranges that should be excluded from analysis. This is done to focus on potentially suspicious activity from untrusted sources.
Private IP Ranges: It specifies a list of private IP address ranges that are typically used within internal networks and are not considered suspicious.
High-Risk Actions: It identifies a set of actions considered high-risk, such as file downloads, deletions, and data exfiltration.
Identify SPs Bypassing Conditional Access: The query looks for service principals that have bypassed conditional access policies within the last two hours. It filters out any sign-ins from trusted IPs and private ranges, focusing on those that resulted in a "notApplied" or "failure" status for conditional access.
Correlate with High-Risk Activity: It then checks if these service principals performed any high-risk actions within one hour after bypassing conditional access. This is done by joining the bypassed SPs with cloud app events that include high-risk actions.
Summarize Findings: The query summarizes the findings by counting the number of high-risk actions, listing the types of actions, affected objects, applications involved, and other relevant details. It only reports cases where at least two high-risk actions were detected.
Overall, this query is used to identify and investigate potential security incidents where service principals might be used to bypass security controls and perform unauthorized or risky operations in the cloud environment.

David Alonso
Released: April 21, 2026
Tables
Keywords
Operators