Detection rules › Kusto

Failed AzureAD logons but success logon to host

Severity
medium
Time window
1d
Author
Microsoft Security Research
Source
github.com/Azure/Azure-Sentinel

Identifies a list of IP addresses with a minimum number (default of 5) of failed logon attempts to Microsoft Entra ID. Uses that list to identify any successful remote logons to hosts from these IPs within the same timeframe.

MITRE ATT&CK coverage

TacticTechniques
Initial Access
Credential Access

Telemetry coverage

Rule body

id:  8ee967a2-a645-4832-85f4-72b635bcb3a6
name: Failed AzureAD logons but success logon to host
description: |
  'Identifies a list of IP addresses with a minimum number (default of 5) of failed logon attempts to Microsoft Entra ID.
  Uses that list to identify any successful remote logons to hosts from these IPs within the same timeframe.'
severity: Medium
requiredDataConnectors:
  - connectorId: AzureActiveDirectory
    dataTypes:
     - SigninLogs
  - connectorId: AzureActiveDirectory
    dataTypes:
      - AADNonInteractiveUserSignInLogs
  - connectorId: SecurityEvents
    dataTypes:
      - SecurityEvent
  - connectorId: Syslog
    dataTypes:
      - Syslog
  - connectorId: WindowsSecurityEvents
    dataTypes:
      - SecurityEvents
  - connectorId: WindowsForwardedEvents
    dataTypes:
      - WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - InitialAccess
  - CredentialAccess
relevantTechniques:
  - T1078
  - T1110
query: |
  //Adjust this threshold to fit the environment
  let signin_threshold = 5;
  //Make a list of all IPs with failed signins to AAD above our threshold
  let aadFunc = (tableName:string){
  let suspicious_signins =
  table(tableName)
  | where ResultType !in ("0", "50125", "50140")
  | where IPAddress !in ('127.0.0.1', '::1', '')
  | summarize count() by IPAddress
  | where count_ > signin_threshold
  | summarize make_set(IPAddress);
  //See if any of these IPs have sucessfully logged into *nix hosts
  let linux_logons =
  Syslog
  | where Facility contains "auth" and ProcessName != "sudo"
  | where SyslogMessage has "Accepted"
  | extend SourceIP = extract("(([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.(([0-9]{1,3})))",1,SyslogMessage)
  | where SourceIP in (suspicious_signins)
  | extend Reason = "Multiple failed AAD logins from IP address"
  | project TimeGenerated, Computer, HostIP, IpAddress = SourceIP, SyslogMessage, Facility, ProcessName, Reason;
  //See if any of these IPs have sucessfully logged into Windows hosts
  let win_logons = (union isfuzzy=true
  (SecurityEvent
  | where EventID == 4624
  | where LogonType in (10, 7, 3)
  | where IpAddress != "-"
  | where IpAddress in (suspicious_signins)
  | extend Reason = "Multiple failed AAD logins from IP address"
  | project TimeGenerated, Account, AccountType, Computer, Activity, EventID, LogonProcessName, IpAddress, LogonTypeName, TargetUserSid, TargetUserName, TargetDomainName, _ResourceId, Reason
  ),
  (WindowsEvent
  | where EventID == 4624 and has_any_ipv4(EventData, toscalar(suspicious_signins))
  | extend LogonType = tostring(EventData.LogonType)
  | where LogonType in (10, 7, 3)
  | extend  IpAddress = tostring(EventData.IpAddress)
  | where IpAddress != "-"
  | where IpAddress in (suspicious_signins)
  | extend Reason = "Multiple failed AAD logins from IP address"
  | extend Activity = "4624 - An account was successfully logged on."
  | extend TargetUserName = tostring(EventData.TargetUserName), TargetDomainName = tostring(EventData.TargetDomainName)
  | extend Account =  strcat(TargetDomainName,"\\", TargetUserName)
  | extend TargetUserSid = tostring(EventData.TargetUserSid)
  | extend TargetAccount = strcat(EventData.TargetDomainName,"\\", EventData.TargetUserName)
  | extend AccountType =case(Account endswith "$" or TargetUserSid in ("S-1-5-18", "S-1-5-19", "S-1-5-20"), "Machine", isempty(TargetUserSid), "", "User")
  | extend LogonProcessName = tostring(EventData.LogonProcessName)
  | project TimeGenerated, Account, AccountType, Computer, Activity, EventID, LogonProcessName, IpAddress, TargetUserSid, TargetUserName, TargetDomainName, _ResourceId, Reason
  )
  );
  union isfuzzy=true linux_logons,win_logons
  | extend timestamp = TimeGenerated
  | extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
  | extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex+1), Computer)
  };
  let aadSignin = aadFunc("SigninLogs");
  let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");
  union isfuzzy=true aadSignin, aadNonInt
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: FullName
        columnName: Account
      - identifier: Name
        columnName: TargetUserName
      - identifier: NTDomain
        columnName: TargetDomainName
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: Computer
      - identifier: HostName
        columnName: HostName
      - identifier: NTDomain
        columnName: HostNameDomain
  - entityType: Host
    fieldMappings:
      - identifier: AzureID
        columnName: _ResourceId
  - entityType: IP
    fieldMappings:
      - identifier: Address
        columnName: IpAddress
version: 1.3.2
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 signin_threshold = 5;
let aadFunc = (tableName:string){
let suspicious_signins =
table(tableName)
| where ResultType !in ("0", "50125", "50140")
| where IPAddress !in ('127.0.0.1', '::1', '')
| summarize count() by IPAddress
| where count_ > signin_threshold
| summarize make_set(IPAddress);
let linux_logons = Syslog <inlined as stages below>;
let win_logons = (union <inlined as stages below>;
let aadSignin = aadFunc("SigninLogs");
let aadNonInt = aadFunc("AADNonInteractiveUserSignInLogs");

Stage 1: source

let aadSignin

Stage 2: source

let aadNonInt

Stage 3: union

union of 2 branches

Stage 4: union

union of 2 branches

Stage 5: source

Syslog

Stage 6: where

where Facility contains "auth" and ProcessName !~ "sudo"

Stage 7: where

where SyslogMessage contains "Accepted"

Stage 8: extend

extend SourceIP

Stage 9: where

where SourceIP =~ "suspicious_signins"

Stage 10: extend

extend Reason

Stage 11: project

project Computer, Facility, HostIP, IpAddress, ProcessName, Reason, SyslogMessage, TimeGenerated

Stage 12: union

union of 2 branches

Stage 13: source

SecurityEvent

Stage 14: where

where EventID == 4624

Stage 15: where

where LogonType in~ (10, 3, 7)

Stage 16: where

where IpAddress !~ "-"

Stage 17: where

where IpAddress =~ "suspicious_signins"

Stage 18: extend

extend Reason

Stage 19: project

project Account, AccountType, Activity, Computer, EventID, IpAddress, LogonProcessName, LogonTypeName, Reason, TargetDomainName, TargetUserName, TargetUserSid, TimeGenerated, _ResourceId

Stage 20: source

WindowsEvent

Stage 21: where

where EventID == 4624

Stage 22: extend

extend LogonType

Stage 23: where

where LogonType in~ (10, 3, 7)

Stage 24: extend

extend IpAddress

Stage 25: where

where IpAddress !~ "-"

Stage 26: where

where IpAddress =~ "suspicious_signins"

Stage 27: extend (8 consecutive steps)

extend Account, AccountType, Activity, LogonProcessName, Reason, TargetAccount, TargetDomainName, TargetUserName, TargetUserSid

Stage 28: project

project Account, AccountType, Activity, Computer, EventID, IpAddress, LogonProcessName, Reason, TargetDomainName, TargetUserName, TargetUserSid, TimeGenerated, _ResourceId

Stage 29: extend (3 consecutive steps)

extend DomainIndex, HostName, HostNameDomain, timestamp

Stage 30: union

union of 2 branches

Stage 31: source

Syslog

Stage 32: where

where Facility contains "auth" and ProcessName !~ "sudo"

Stage 33: where

where SyslogMessage contains "Accepted"

Stage 34: extend

extend SourceIP

Stage 35: where

where SourceIP =~ "suspicious_signins"

Stage 36: extend

extend Reason

Stage 37: project

project Computer, Facility, HostIP, IpAddress, ProcessName, Reason, SyslogMessage, TimeGenerated

Stage 38: union

union of 2 branches

Stage 39: source

SecurityEvent

Stage 40: where

where EventID == 4624

Stage 41: where

where LogonType in~ (10, 3, 7)

Stage 42: where

where IpAddress !~ "-"

Stage 43: where

where IpAddress =~ "suspicious_signins"

Stage 44: extend

extend Reason

Stage 45: project

project Account, AccountType, Activity, Computer, EventID, IpAddress, LogonProcessName, LogonTypeName, Reason, TargetDomainName, TargetUserName, TargetUserSid, TimeGenerated, _ResourceId

Stage 46: source

WindowsEvent

Stage 47: where

where EventID == 4624

Stage 48: extend

extend LogonType

Stage 49: where

where LogonType in~ (10, 3, 7)

Stage 50: extend

extend IpAddress

Stage 51: where

where IpAddress !~ "-"

Stage 52: where

where IpAddress =~ "suspicious_signins"

Stage 53: extend (8 consecutive steps)

extend Account, AccountType, Activity, LogonProcessName, Reason, TargetAccount, TargetDomainName, TargetUserName, TargetUserSid

Stage 54: project

project Account, AccountType, Activity, Computer, EventID, IpAddress, LogonProcessName, Reason, TargetDomainName, TargetUserName, TargetUserSid, TimeGenerated, _ResourceId

Stage 55: extend (3 consecutive steps)

extend DomainIndex, HostName, HostNameDomain, timestamp

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
EventIDeq
  • 4624 corpus 29 (splunk 13, kusto 11, chronicle 4, elastic 1)
field:"EventID" kind:eq value:"4624"
Facilitycontains
  • auth
field:"Facility" kind:contains value:"auth"
IpAddressin
  • suspicious_signins
field:"src_ip" kind:in value:"suspicious_signins"
IpAddressne
  • - corpus 3 (splunk 2, kusto 1)
field:"src_ip" kind:ne value:"-"
LogonTypein
  • 10 corpus 8 (kusto 4, sigma 3, splunk 1)
  • 3 corpus 41 (splunk 13, sigma 12, elastic 9, kusto 7)
  • 7
field:"LogonType" kind:in
ProcessNamene
  • sudo
field:"process_name" kind:ne value:"sudo"
SourceIPin
  • suspicious_signins corpus 2 (kusto 2)
field:"SourceIP" kind:in value:"suspicious_signins"
SyslogMessagematch
  • Accepted transforms: term
field:"SyslogMessage" kind:match value:"Accepted"

Output fields

These fields are emitted when the rule matches.

FieldSource
Accountproject
AccountTypeproject
Activityproject
Computerproject
EventIDproject
IpAddressproject
LogonProcessNameproject
Reasonproject
TargetDomainNameproject
TargetUserNameproject
TargetUserSidproject
TimeGeneratedproject
_ResourceIdproject
timestampextend
DomainIndexextend
HostNameextend
HostNameDomainextend