Query Details
id: e3f4a5b6-c7d8-4e9f-0a1b-2c3d4e5f6a7b
name: "SigninLogs — Credential Stuffing Attack (High-Velocity Invalid Credentials)"
version: 1.0.0
kind: Scheduled
description: |
Detects credential stuffing attacks: a single IP generating 50 or more ResultType 50126 errors (invalid username or password) against multiple accounts. Attackers use credential lists from previous data breaches. MITRE ATT&CK: T1110.004 (Credential Stuffing).
severity: High
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
queryFrequency: PT6H
queryPeriod: P7D
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110
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 velocityThreshold = 50;
SigninLogs
| invoke _ExcludeAllowlistedIPs()
| where TimeGenerated > ago(7d)
| where ResultType == "50126"
| summarize
InvalidCredAttempts = count(),
UniqueUsers = dcount(UserPrincipalName),
Users = make_set(UserPrincipalName, 50),
StartTime = min(TimeGenerated),
EndTime = max(TimeGenerated),
Locations = make_set(Location, 5)
by IPAddress
| where InvalidCredAttempts >= velocityThreshold
| project
TimeGenerated = StartTime,
IPAddress, InvalidCredAttempts, UniqueUsers,
Users, EndTime, Locations
entityMappings:
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
customDetails:
InvalidCredAttempts: InvalidCredAttempts
UniqueUsers: UniqueUsers
alertDetailsOverride:
alertDisplayNameFormat: "Credential Stuffing from {{IPAddress}} — {{InvalidCredAttempts}} attempts"
alertDescriptionFormat: "{{UniqueUsers}} accounts targeted with invalid credentials (50126) from {{IPAddress}} over 7 days."
incidentConfiguration:
createIncident: true
groupingConfiguration:
enabled: true
reopenClosedIncident: false
lookbackDuration: PT1H
matchingMethod: AllEntities
groupByEntities:
- IP
This query is designed to detect credential stuffing attacks, which occur when a single IP address generates 50 or more invalid login attempts (ResultType 50126 errors) across multiple user accounts within a 7-day period. Credential stuffing involves using lists of stolen credentials from previous data breaches to try and gain unauthorized access to accounts.
Here's a simplified breakdown of the query:
Allowlist Exclusion: It first sets up a mechanism to exclude trusted IP addresses or ranges (allowlist) from the analysis. This is done to focus only on potentially malicious activity.
Threshold Setting: The query defines a threshold of 50 invalid login attempts from a single IP address to flag potential attacks.
Data Filtering: It filters the SigninLogs data to include only entries from the last 7 days with a ResultType of "50126" (indicating invalid username or password).
Data Aggregation: The query aggregates data by IP address, counting the number of invalid login attempts, the number of unique user accounts targeted, and the locations from which these attempts originated. It also records the start and end times of these attempts.
Alert Generation: If an IP address exceeds the threshold of 50 invalid attempts, an alert is generated. The alert includes details such as the number of attempts, the number of unique users targeted, and the IP address involved.
Incident Configuration: The query is set to create an incident if an alert is triggered, with specific configurations for grouping and reopening incidents.
Overall, this query helps identify and alert on high-velocity credential stuffing attacks, allowing for timely investigation and response to potential security threats.

David Alonso
Released: April 20, 2026
Tables
Keywords
Operators