Detection rules › Kusto
Password Spraying
This query detects a password spraying attack, where a single machine has performed a large number of failed login attempts, with a large number of different accounts. For each account, the attacker uses just a few attempts to prevent account lockout. This query uses the DeviceLogonEvents per machine to detect a password spraying attacks. The machine against which the password spraying is performed (can be DC, a server or even an endpoint) needs to be enrolled in Microsoft Defender for Endpoint.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Credential Access |
Telemetry coverage
Rule body
id: e00f72ab-fea1-4a31-9ecc-eea6397cd38d
name: Password Spraying
description: |
This query detects a password spraying attack, where a single machine has performed a large number of failed login attempts, with a large number of different accounts.
For each account, the attacker uses just a few attempts to prevent account lockout. This query uses the DeviceLogonEvents per machine to detect a password spraying attacks.
The machine against which the password spraying is performed (can be DC, a server or even an endpoint) needs to be enrolled in Microsoft Defender for Endpoint.
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- DeviceLogonEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CredentialAccess
relevantTechniques:
- T1110.003
query: |
let thresholdForUniqueFailedAccounts = 20;
let upperBoundOfFailedLogonsPerAccount = 10;
let ratioSuccessFailedLogons = 0.5;
let timeframe = 1d;
DeviceLogonEvents
| where Timestamp >= ago(timeframe)
| where LogonType != "Unlock" and ActionType in ("LogonSuccess", "LogonFailed")
| where not(isempty( RemoteIP) and isempty( RemoteDeviceName))
| extend LocalLogon=parse_json(AdditionalFields)
| where RemoteIPType != "Loopback"
| summarize SuccessLogonCount = countif(ActionType == "LogonSuccess"), FailedLogonCount = countif(ActionType == "LogonFailed"),
UniqueAccountFailedLogons=dcountif(AccountName, ActionType == "LogonFailed"), FirstFailed=minif(Timestamp, ActionType == "LogonFailed"),
LastFailed=maxif(Timestamp, ActionType == "LogonFailed"), LastTimestamp=arg_max(Timestamp, tostring(ReportId)) by RemoteIP, DeviceName // RemoteIP is the source of the logon attempt.
| project-rename IPAddress=RemoteIP
| where UniqueAccountFailedLogons > thresholdForUniqueFailedAccounts and SuccessLogonCount*ratioSuccessFailedLogons < FailedLogonCount and UniqueAccountFailedLogons*upperBoundOfFailedLogonsPerAccount > FailedLogonCount
entityMappings:
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- entityType: IP
fieldMappings:
- identifier: Address
columnName: IPAddress
version: 1.0.1
kind: Scheduled
Stages and Predicates
Parameters
let thresholdForUniqueFailedAccounts = 20;
let upperBoundOfFailedLogonsPerAccount = 10;
let ratioSuccessFailedLogons = 0.5;
let timeframe = 1d;
Stage 1: source
DeviceLogonEvents
Stage 2: where
| where Timestamp >= ago(timeframe)
Stage 3: where
| where LogonType != "Unlock" and ActionType in ("LogonSuccess", "LogonFailed")
Stage 4: where
| where not(isempty( RemoteIP) and isempty( RemoteDeviceName))
Stage 5: extend
| extend LocalLogon=parse_json(AdditionalFields)
Stage 6: where
| where RemoteIPType != "Loopback"
Stage 7: summarize
| summarize SuccessLogonCount = countif(ActionType == "LogonSuccess"), FailedLogonCount = countif(ActionType == "LogonFailed"),
UniqueAccountFailedLogons=dcountif(AccountName, ActionType == "LogonFailed"), FirstFailed=minif(Timestamp, ActionType == "LogonFailed"),
LastFailed=maxif(Timestamp, ActionType == "LogonFailed"), LastTimestamp=arg_max(Timestamp, tostring(ReportId)) by RemoteIP, DeviceName
Stage 8: project-rename
| project-rename IPAddress=RemoteIP
Stage 9: where
| where UniqueAccountFailedLogons > thresholdForUniqueFailedAccounts and SuccessLogonCount*ratioSuccessFailedLogons < FailedLogonCount and UniqueAccountFailedLogons*upperBoundOfFailedLogonsPerAccount > FailedLogonCount
Exclusions
The rule actively suppresses these predicates.
| Field | Kind | Excluded values | Search |
|---|---|---|---|
RemoteDeviceName | is_null | excludes:RemoteDeviceName | |
RemoteIP | is_null | excludes:RemoteIP |
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
ActionType | in |
| field:"ActionType" kind:in |
LogonType | ne |
| field:"LogonType" kind:ne value:"Unlock" |
RemoteIPType | ne |
| field:"RemoteIPType" kind:ne value:"Loopback" |
SuccessLogonCount | cross_field_compare |
| field:"SuccessLogonCount" kind:cross_field_compare value:"FailedLogonCount" |
UniqueAccountFailedLogons | cross_field_compare |
| field:"UniqueAccountFailedLogons" kind:cross_field_compare value:"FailedLogonCount" |
UniqueAccountFailedLogons | gt |
| field:"UniqueAccountFailedLogons" kind:gt value:"20" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
DeviceName | summarize |
FailedLogonCount | summarize |
FirstFailed | summarize |
LastFailed | summarize |
LastTimestamp | summarize |
RemoteIP | summarize |
SuccessLogonCount | summarize |
UniqueAccountFailedLogons | summarize |
IPAddress | project-rename |