KQL Authentication Queries

Failed Sign-Ins (Last 24h)

SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType != "0"
| summarize
    FailCount = count(),
    DistinctIPs = dcount(IPAddress),
    LastAttempt = max(TimeGenerated)
    by UserPrincipalName, ResultDescription
| sort by FailCount desc
| take 20

Brute Force Detection

let threshold = 10;
let timeWindow = 1h;
SigninLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == "50126"  // Invalid username or password
| summarize
    Attempts = count(),
    DistinctIPs = dcount(IPAddress),
    IPs = make_set(IPAddress),
    FirstAttempt = min(TimeGenerated),
    LastAttempt = max(TimeGenerated)
    by UserPrincipalName
| where Attempts > threshold
| sort by Attempts desc

Password Spray Detection

let timeWindow = 1h;
let userThreshold = 5;
SigninLogs
| where TimeGenerated > ago(timeWindow)
| where ResultType == "50126"
| summarize
    TargetUsers = dcount(UserPrincipalName),
    Users = make_set(UserPrincipalName),
    Attempts = count()
    by IPAddress
| where TargetUsers > userThreshold
| sort by TargetUsers desc

MFA Analysis

// MFA failures
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "50074"  // MFA required
   or ResultType == "50076"    // MFA denied
| summarize count() by UserPrincipalName, ResultDescription
| sort by count_ desc

// Users without MFA
SigninLogs
| where TimeGenerated > ago(7d)
| where AuthenticationRequirement == "singleFactorAuthentication"
| summarize count() by UserPrincipalName, AppDisplayName
| sort by count_ desc

Conditional Access Failures

SigninLogs
| where TimeGenerated > ago(24h)
| where ConditionalAccessStatus == "failure"
| summarize count() by
    UserPrincipalName,
    AppDisplayName,
    ConditionalAccessPolicies
| sort by count_ desc

Impossible Travel

let timeThreshold = 60m;
SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType == "0"
| project TimeGenerated, UserPrincipalName, IPAddress, Location
| sort by UserPrincipalName, TimeGenerated asc
| extend PrevLocation = prev(Location), PrevTime = prev(TimeGenerated), PrevUser = prev(UserPrincipalName)
| where UserPrincipalName == PrevUser
| where Location != PrevLocation
| where datetime_diff('minute', TimeGenerated, PrevTime) < 60
| project TimeGenerated, UserPrincipalName, IPAddress, Location, PrevLocation, TimeDiff = datetime_diff('minute', TimeGenerated, PrevTime)

Successful Logins from Unusual Locations

let knownLocations = dynamic(["US", "United States"]);
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "0"
| where Location !in (knownLocations)
| summarize count() by UserPrincipalName, Location, IPAddress
| sort by count_ desc

Service Account Activity

SigninLogs
| where TimeGenerated > ago(24h)
| where UserPrincipalName startswith "svc-"
   or UserPrincipalName startswith "app-"
| summarize
    Logins = count(),
    UniqueIPs = dcount(IPAddress),
    Apps = make_set(AppDisplayName)
    by UserPrincipalName
| sort by Logins desc