Detection rules › Kusto

Failed logon attempts by valid accounts within 10 mins

Severity
low
Time window
10m
Group by
Account, Activity, Computer, EventID, IpAddress, LogonProcessName, LogonType, LogonTypeName, Reason, ResourceId, SourceComputerId, Status, SubStatus, TargetAccount, TargetDomainName, TargetUserName, WorkstationName
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies when failed logon attempts are 20 or higher during a 10 minute period (2 failed logons per minute minimum) from valid account.

MITRE ATT&CK coverage

TacticTechniques
Credential Access

Telemetry coverage

Rule body

id: 0777f138-e5d8-4eab-bec1-e11ddfbc2be2
name: Failed logon attempts by valid accounts within 10 mins
description: |
  'Identifies when failed logon attempts are 20 or higher during a 10 minute period (2 failed logons per minute minimum) from valid account.'
severity: Low
requiredDataConnectors:
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 10m
queryPeriod: 10m
triggerOperator: gt
triggerThreshold: 0
tactics:
  - CredentialAccess
relevantTechniques:
  - T1110
query: |
  let threshold = 20;
  let ReasontoSubStatus = datatable(SubStatus: string, Reason: string) [
      "0xc000005e", "There are currently no logon servers available to service the logon request.",
      "0xc0000064", "User logon with misspelled or bad user account",
      "0xc000006a", "User logon with misspelled or bad password",
      "0xc000006d", "Bad user name or password",
      "0xc000006e", "Unknown user name or bad password",
      "0xc000006f", "User logon outside authorized hours",
      "0xc0000070", "User logon from unauthorized workstation",
      "0xc0000071", "User logon with expired password",
      "0xc0000072", "User logon to account disabled by administrator",
      "0xc00000dc", "Indicates the Sam Server was in the wrong state to perform the desired operation",
      "0xc0000133", "Clocks between DC and other computer too far out of sync",
      "0xc000015b", "The user has not been granted the requested logon type (aka logon right) at this machine",
      "0xc000018c", "The logon request failed because the trust relationship between the primary domain and the trusted domain failed",
      "0xc0000192", "An attempt was made to logon, but the Netlogon service was not started",
      "0xc0000193", "User logon with expired account",
      "0xc0000224", "User is required to change password at next logon",
      "0xc0000225", "Evidently a bug in Windows and not a risk",
      "0xc0000234", "User logon with account locked",
      "0xc00002ee", "Failure Reason: An Error occurred during Logon",
      "0xc0000413", "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine"
  ];
  (union isfuzzy=true
      (SecurityEvent
      | where EventID == 4625
      | where AccountType =~ "User"
      | where SubStatus !~ '0xc0000064' and Account !in ('\\', '-\\-')
      // SubStatus '0xc0000064' signifies 'Account name does not exist'
      | extend
          ResourceId = column_ifexists("_ResourceId", _ResourceId),
          SourceComputerId = column_ifexists("SourceComputerId", SourceComputerId),
          SubStatus = tolower(SubStatus)
      | lookup ReasontoSubStatus on SubStatus
      | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
          Activity, Computer, Account, TargetAccount, TargetUserName, TargetDomainName,
          LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
      | where FailedLogonCount >= threshold
      ),
      (
      (WindowsEvent
      | where EventID == 4625 and not(EventData has '0xc0000064')
      | extend TargetAccount = strcat(tostring(EventData.TargetDomainName), "\\", tostring(EventData.TargetUserName))
      | extend TargetUserSid = tostring(EventData.TargetUserSid)
      | extend AccountType=case(EventData.TargetUserName endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
      | where AccountType =~ "User"
      | extend SubStatus = tostring(EventData.SubStatus)
      | where SubStatus !~ '0xc0000064' and TargetAccount !in ('\\', '-\\-')
      // SubStatus '0xc0000064' signifies 'Account name does not exist'
      | extend
          ResourceId = column_ifexists("_ResourceId", _ResourceId),
          SourceComputerId = column_ifexists("SourceComputerId", ""),
          SubStatus = tolower(SubStatus)
      | lookup ReasontoSubStatus on SubStatus
      | extend coalesce(Reason, strcat('Unknown reason substatus: ', SubStatus))
      | extend Activity="4625 - An account failed to log on."
      | extend TargetUserName = tostring(EventData.TargetUserName)
      | extend TargetDomainName = tostring(EventData.TargetDomainName)
      | extend LogonType = tostring(EventData.LogonType)
      | extend Status= tostring(EventData.Status)
      | extend LogonProcessName = tostring(EventData.LogonProcessName)
      | extend WorkstationName = tostring(EventData.WorkstationName)
      | extend IpAddress = tostring(EventData.IpAddress)
      | extend LogonTypeName=case(
        LogonType == 2, "2 - Interactive",
        LogonType == 3, "3 - Network",
        LogonType == 4, "4 - Batch",
        LogonType == 5, "5 - Service",
        LogonType == 7, "7 - Unlock",
        LogonType == 8, "8 - NetworkCleartext",
        LogonType == 9, "9 - NewCredentials",
        LogonType == 10, "10 - RemoteInteractive",
        LogonType == 11, "11 - CachedInteractive",
        tostring(LogonType)
      )
      | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), FailedLogonCount = count() by bin(TimeGenerated,10m), EventID,
          Activity, Computer, TargetAccount, TargetUserName, TargetDomainName,
          LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress
      | where FailedLogonCount >= threshold
      )))
  | summarize arg_max(TimeGenerated, *) by Computer, TargetAccount, TargetUserName, TargetDomainName
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: TargetAccount
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.2.5
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Microsoft Security Research
    support:
        tier: Community
    categories:
        domains: [ "Security - Others", "Identity" ]

Stages and Predicates

Stage 0: let

let threshold = 20;
let ReasontoSubStatus = datatable(SubStatus: string, Reason: string) [
    "0xc000005e", "There are currently no logon servers available to service the logon request.",
    "0xc0000064", "User logon with misspelled or bad user account",
    "0xc000006a", "User logon with misspelled or bad password",
    "0xc000006d", "Bad user name or password",
    "0xc000006e", "Unknown user name or bad password",
    "0xc000006f", "User logon outside authorized hours",
    "0xc0000070", "User logon from unauthorized workstation",
    "0xc0000071", "User logon with expired password",
    "0xc0000072", "User logon to account disabled by administrator",
    "0xc00000dc", "Indicates the Sam Server was in the wrong state to perform the desired operation",
    "0xc0000133", "Clocks between DC and other computer too far out of sync",
    "0xc000015b", "The user has not been granted the requested logon type (aka logon right) at this machine",
    "0xc000018c", "The logon request failed because the trust relationship between the primary domain and the trusted domain failed",
    "0xc0000192", "An attempt was made to logon, but the Netlogon service was not started",
    "0xc0000193", "User logon with expired account",
    "0xc0000224", "User is required to change password at next logon",
    "0xc0000225", "Evidently a bug in Windows and not a risk",
    "0xc0000234", "User logon with account locked",
    "0xc00002ee", "Failure Reason: An Error occurred during Logon",
    "0xc0000413", "Logon Failure: The machine you are logging onto is protected by an authentication firewall. The specified account is not allowed to authenticate to the machine"
];

Stage 1: union

union of 2 branches

Stage 2: source

SecurityEvent

Stage 3: where

| where EventID == 4625

Stage 4: where

| where AccountType =~ "User"

Stage 5: where

| where SubStatus !~ '0xc0000064' and Account !in ('\\', '-\\-')

Stage 6: extend

| extend
        ResourceId = column_ifexists("_ResourceId", _ResourceId),
        SourceComputerId = column_ifexists("SourceComputerId", SourceComputerId),
        SubStatus = tolower(SubStatus)

Stage 7: kusto:lookup

lookup (ReasontoSubStatus) on SubStatus

Stage 8: extend

extend

Stage 9: summarize

summarize EndTime, FailedLogonCount, StartTime by EventID, Activity, Computer, Account, TargetAccount, TargetUserName, TargetDomainName, LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress

Stage 10: where

where FailedLogonCount >= 20

Stage 11: source

WindowsEvent

Stage 12: where

| where EventID == 4625 and not(EventData has '0xc0000064')

Stage 13: extend (3 consecutive steps)

| extend TargetAccount = strcat(tostring(EventData.TargetDomainName), "\\", tostring(EventData.TargetUserName))
| extend TargetUserSid = tostring(EventData.TargetUserSid)
| extend AccountType=case(EventData.TargetUserName endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")

Stage 14: where

| where AccountType =~ "User"

Stage 15: extend

| extend SubStatus = tostring(EventData.SubStatus)

Stage 16: where

| where SubStatus !~ '0xc0000064' and TargetAccount !in ('\\', '-\\-')

Stage 17: extend

| extend
        ResourceId = column_ifexists("_ResourceId", _ResourceId),
        SourceComputerId = column_ifexists("SourceComputerId", ""),
        SubStatus = tolower(SubStatus)

Stage 18: kusto:lookup

lookup (ReasontoSubStatus) on SubStatus

Stage 19: extend (10 consecutive steps)

extend Activity, IpAddress, LogonProcessName, LogonType, LogonTypeName, Status, TargetDomainName, TargetUserName, WorkstationName

Stage 20: summarize

summarize EndTime, FailedLogonCount, StartTime by EventID, Activity, Computer, TargetAccount, TargetUserName, TargetDomainName, LogonType, LogonTypeName, LogonProcessName, Status, SubStatus, Reason, ResourceId, SourceComputerId, WorkstationName, IpAddress

Stage 21: where

where FailedLogonCount >= 20

Stage 22: summarize

summarize by Computer, TargetAccount, TargetUserName, TargetDomainName

Stage 23: extend

extend DomainIndex, HostName

Stage 24: extend

extend HostNameDomain
HostNameDomain =
ifDomainIndex != -1substring(Computer, (DomainIndex + 1))
elseComputer

Indicators

These rows show field, operator, and value matches.

Exclusions

The rule actively suppresses these predicates.

Output fields

These fields are emitted when the rule matches.

FieldSource
Computersummarize
TargetAccountsummarize
TargetDomainNamesummarize
TargetUserNamesummarize
DomainIndexextend
HostNameextend
HostNameDomainextend