Detection rules › Kusto
Risky user signin observed in non-Microsoft network device
'This content is utilized to identify instances of successful login by risky users, who have been observed engaging in potentially suspicious network activity on non-Microsoft network devices.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Command & Control | T1071 Application Layer Protocol |
Rules detecting the same action
Other rules on this platform that filter on the same API call or operation.
- Anomalous sign-in location by user account and authenticating application (Kusto)
- Anomalous Single Factor Signin (Kusto)
- Authentications of Privileged Accounts Outside of Expected Controls (Kusto)
- Azure Many Failed SignIns (Panther)
- Azure Portal sign in from another Azure Tenant (Kusto)
- Azure Service Principal Sign-In Followed by Arc Cluster Credential Access (Elastic)
- Azure SignIn via Legacy Authentication Protocol (Panther)
- Detect non-admin requesting token for admin applications (Kusto)
Rule body kusto
id: 042f2801-a375-4cfd-bd29-041fc7ed88a0
name: Risky user signin observed in non-Microsoft network device
description: |
'This content is utilized to identify instances of successful login by risky users, who have been observed engaging in potentially suspicious network activity on non-Microsoft network devices.'
severity: Medium
requiredDataConnectors:
- connectorId: AzureActiveDirectory
dataTypes:
- SigninLogs
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog (PaloAlto)
- connectorId: Fortinet
dataTypes:
- CommonSecurityLog (Fortinet)
- connectorId: CheckPoint
dataTypes:
- CommonSecurityLog (CheckPoint)
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog (Zscaler)
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071
query: |
SigninLogs
//Find risky Signin
| where RiskState == "atRisk" and ResultType == 0
| extend Signin_Time = TimeGenerated
| summarize
AppDisplayName=make_set(AppDisplayName),
ClientAppUsed=make_set(ClientAppUsed),
UserAgent=make_set(UserAgent),
CorrelationId=make_set(CorrelationId),
Signin_Time= min(Signin_Time),
RiskEventTypes=make_set(RiskEventTypes)
by
ConditionalAccessStatus,
IPAddress,
IsRisky,
ResourceDisplayName,
RiskDetail,
ResultType,
RiskLevelAggregated,
RiskLevelDuringSignIn,
RiskState,
UserPrincipalName=tostring(tolower(UserPrincipalName)),
SourceSystem
| join kind=inner (
CommonSecurityLog
| where DeviceVendor has_any ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
| where DeviceProduct startswith "FortiGate" or DeviceProduct startswith "PAN" or DeviceProduct startswith "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith "NSSWeblog" or DeviceProduct startswith "URL"
| where DeviceAction != "Block"
| where isnotempty(RequestURL)
| where isnotempty(SourceUserName)
| extend SourceUserName = tolower(SourceUserName)
| summarize
min(TimeGenerated),
max(TimeGenerated),
Activity=make_set(Activity)
by DestinationHostName, DestinationIP, RequestURL, SourceUserName=tostring(tolower(SourceUserName)),DeviceVendor,DeviceProduct
| extend 3p_observed_Time= min_TimeGenerated,Name = tostring(split(SourceUserName,"@")[0]),UPNSuffix =tostring(split(SourceUserName,"@")[1]))
on $left.IPAddress == $right.DestinationIP and $left.UserPrincipalName == $right.SourceUserName
| extend Timediff = datetime_diff('day', 3p_observed_Time, Signin_Time)
| where Timediff <= 1 and Timediff >= 0
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: UserPrincipalName
- identifier: Name
columnName: Name
- identifier: UPNSuffix
columnName: UPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
- entityType: DNS
fieldMappings:
- identifier: DomainName
columnName: DestinationHostName
kind: Scheduled
version: 1.0.6
metadata:
source:
kind: Community
author:
name: Arjun Trivedi
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Stages and Predicates
Stage 1: source
SigninLogs
Stage 2: where
| where RiskState == "atRisk" and ResultType == 0
Stage 3: extend
| extend Signin_Time = TimeGenerated
Stage 4: summarize
| summarize
AppDisplayName=make_set(AppDisplayName),
ClientAppUsed=make_set(ClientAppUsed),
UserAgent=make_set(UserAgent),
CorrelationId=make_set(CorrelationId),
Signin_Time= min(Signin_Time),
RiskEventTypes=make_set(RiskEventTypes)
by
ConditionalAccessStatus,
IPAddress,
IsRisky,
ResourceDisplayName,
RiskDetail,
ResultType,
RiskLevelAggregated,
RiskLevelDuringSignIn,
RiskState,
UserPrincipalName=tostring(tolower(UserPrincipalName)),
SourceSystem
Stage 5: join
| join kind=inner (
CommonSecurityLog
| where DeviceVendor has_any ("Palo Alto Networks", "Fortinet", "Check Point", "Zscaler")
| where DeviceProduct startswith "FortiGate" or DeviceProduct startswith "PAN" or DeviceProduct startswith "VPN" or DeviceProduct startswith "FireWall" or DeviceProduct startswith "NSSWeblog" or DeviceProduct startswith "URL"
| where DeviceAction != "Block"
| where isnotempty(RequestURL)
| where isnotempty(SourceUserName)
| extend SourceUserName = tolower(SourceUserName)
| summarize
min(TimeGenerated),
max(TimeGenerated),
Activity=make_set(Activity)
by DestinationHostName, DestinationIP, RequestURL, SourceUserName=tostring(tolower(SourceUserName)),DeviceVendor,DeviceProduct
| extend 3p_observed_Time= min_TimeGenerated,Name = tostring(split(SourceUserName,"@")[0]),UPNSuffix =tostring(split(SourceUserName,"@")[1]))
on $left.IPAddress == $right.DestinationIP and $left.UserPrincipalName == $right.SourceUserName
Stage 6: extend
| extend Timediff = datetime_diff('day', 3p_observed_Time, Signin_Time)
Stage 7: where
| where Timediff <= 1 and Timediff >= 0
Indicators
Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.
| Field | Kind | Values |
|---|---|---|
DeviceAction | ne |
|
DeviceProduct | starts_with |
|
DeviceVendor | match |
|
RequestURL | is_not_null | |
ResultType | eq |
|
RiskState | eq |
|
SourceUserName | is_not_null | |
Timediff | ge |
|
Timediff | le |
|
Output fields
Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.
| Field | Source |
|---|---|
AppDisplayName | summarize |
ClientAppUsed | summarize |
ConditionalAccessStatus | summarize |
CorrelationId | summarize |
IPAddress | summarize |
IsRisky | summarize |
ResourceDisplayName | summarize |
ResultType | summarize |
RiskDetail | summarize |
RiskEventTypes | summarize |
RiskLevelAggregated | summarize |
RiskLevelDuringSignIn | summarize |
RiskState | summarize |
Signin_Time | summarize |
SourceSystem | summarize |
UserAgent | summarize |
UserPrincipalName | summarize |
Timediff | extend |