Query Details

SigninLogs — Account Enumeration via Error Code Fingerprinting

12 SIGNIN Account Enumeration

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      = 6h;
let minAttempts   = 20;
let enumRatioMin  = 0.40;
SigninLogs
| invoke _ExcludeAllowlistedIPs()
| where TimeGenerated > ago(lookback)
| where ResultType in ("50034", "50126", "50057", "70011")
| where isnotempty(IPAddress)
| summarize
    TotalAttempts   = count(),
    UserNotFound    = countif(ResultType == "50034"),
    InvalidPassword = countif(ResultType == "50126"),
    AccountDisabled = countif(ResultType == "50057"),
    UniqueTargets   = dcount(UserPrincipalName),
    TargetedUsers   = make_set(UserPrincipalName, 30),
    Locations       = make_set(Location, 3),
    FirstAttempt    = min(TimeGenerated),
    LastAttempt     = max(TimeGenerated)
  by IPAddress
| where TotalAttempts >= minAttempts
| extend EnumRatio = round(todouble(UserNotFound) / TotalAttempts, 2)
| where EnumRatio >= enumRatioMin
| project
    TimeGenerated   = FirstAttempt,
    IPAddress,
    TotalAttempts,
    UserNotFound,
    InvalidPassword,
    UniqueTargets,
    EnumRatio,
    TargetedUsers,
    Locations,
    LastAttempt

Explanation

This query is designed to detect potential attackers attempting to discover valid user accounts by analyzing sign-in error codes in Azure Active Directory logs. Here's a simplified explanation:

  1. Purpose: The query identifies suspicious activity where attackers try to find valid user accounts by checking sign-in error codes. Specifically, it looks for error code 50034 (UserNotFound) which indicates non-existent accounts, and error code 50126 which confirms the account exists.

  2. Severity: The query is marked with high severity because it can indicate a precursor to a more targeted attack, such as a password spray attack.

  3. Data Source: It uses data from Azure Active Directory's SigninLogs.

  4. Frequency: The query runs every 6 hours and looks at data from the past 6 hours.

  5. Exclusion of Trusted IPs: It excludes IP addresses that are on a predefined allowlist, which includes trusted IP ranges.

  6. Detection Logic:

    • It checks for a minimum of 20 sign-in attempts from a single IP address.
    • It calculates the ratio of UserNotFound errors to total attempts. If this ratio is 40% or higher, it flags the activity as suspicious.
    • It summarizes the data by IP address, counting total attempts, number of UserNotFound errors, and other details like unique user targets and locations.
  7. Alerting: If suspicious activity is detected, an alert is generated with details about the IP address, the ratio of UserNotFound errors, and the total number of attempts.

  8. Incident Management: If an alert is triggered, an incident is created for further investigation, and similar incidents are grouped by IP address.

Overall, this query helps in identifying and alerting on potential user enumeration attacks, which are often a precursor to more serious credential-based attacks.

Details

David Alonso profile picture

David Alonso

Released: April 20, 2026

Tables

SigninLogs

Keywords

SigninLogsAzureActiveDirectoryUserIPAddressLocationTimeGeneratedUserPrincipalName

Operators

letmaterializeunionisfuzzyprinttakeprojecttostringwhereisnotemptytoscalarmatchesregexextendiffstrcatsummarizemake_listnotarray_lengthisnullipv4_is_in_any_rangemv-applytotypeofonsplitipv4_comparemaxtointproject-awayagoininvokecountcountifdcountmake_setminbyroundtodouble

MITRE Techniques

Actions

GitHub