Detection rules › Kusto

Risky user signin observed in non-Microsoft network device

Severity
medium
Time window
1d
Group by
ConditionalAccessStatus, IPAddress, IsRisky, ResourceDisplayName, ResultType, RiskDetail, RiskLevelAggregated, RiskLevelDuringSignIn, RiskState, SourceSystem, UserPrincipalName
Author
Arjun Trivedi
Source
github.com/Azure/Azure-Sentinel

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

TacticTechniques
Command & Control

Telemetry coverage

PlatformRecord / event type
Entra IDSigninLogs sign-in event: Successful risky sign-in
Microsoft SentinelCommonSecurityLog CEF event: Allowed third-party network activity

Rules detecting the same action

These rules filter on the same operation.

Rule body

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

These rows show field, operator, and value matches.

Output fields

These fields are emitted when the rule matches.

FieldSource
AppDisplayNamesummarize
ClientAppUsedsummarize
ConditionalAccessStatussummarize
CorrelationIdsummarize
IPAddresssummarize
IsRiskysummarize
ResourceDisplayNamesummarize
ResultTypesummarize
RiskDetailsummarize
RiskEventTypessummarize
RiskLevelAggregatedsummarize
RiskLevelDuringSignInsummarize
RiskStatesummarize
Signin_Timesummarize
SourceSystemsummarize
UserAgentsummarize
UserPrincipalNamesummarize
Timediffextend