Detection rules › Kusto
Failed AzureAD logons but success logon to host
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
| Tactic | Techniques |
|---|---|
| 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.
| Field | Kind | Values | Search |
|---|---|---|---|
EventID | eq |
| field:"EventID" kind:eq value:"4624" |
Facility | contains |
| field:"Facility" kind:contains value:"auth" |
IpAddress | in |
| field:"src_ip" kind:in value:"suspicious_signins" |
IpAddress | ne |
| field:"src_ip" kind:ne value:"-" |
LogonType | in |
| field:"LogonType" kind:in |
ProcessName | ne |
| field:"process_name" kind:ne value:"sudo" |
SourceIP | in |
| field:"SourceIP" kind:in value:"suspicious_signins" |
SyslogMessage | match |
| field:"SyslogMessage" kind:match value:"Accepted" |
Output fields
These fields are emitted when the rule matches.
| Field | Source |
|---|---|
Account | project |
AccountType | project |
Activity | project |
Computer | project |
EventID | project |
IpAddress | project |
LogonProcessName | project |
Reason | project |
TargetDomainName | project |
TargetUserName | project |
TargetUserSid | project |
TimeGenerated | project |
_ResourceId | project |
timestamp | extend |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |