Detection rules › Kusto

M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activity

Severity
medium
Time window
1d
Group by
AlertName, ProductName, RequestURL, Url
Author
Arjun Trivedi
Source
github.com/Azure/Azure-Sentinel

'This content is employed to correlate with Microsoft Defender XDR phishing-related alerts. It focuses on instances where a user successfully connects to a phishing URL from a non-Microsoft network device and subsequently makes successful sign-in attempts from the phishing IP address.'

MITRE ATT&CK coverage

TacticTechniques
Privilege EscalationT1078 Valid Accounts

Rule body kusto

id: 779731f7-8ba0-4198-8524-5701b7defddc
name: M365D Alerts Correlation to non-Microsoft Network device network activity involved in successful sign-in Activity
description: |
  'This content is employed to correlate with Microsoft Defender XDR phishing-related alerts. It focuses on instances where a user successfully connects to a phishing URL from a non-Microsoft network device and subsequently makes successful sign-in attempts from the phishing IP address.'
severity: Medium
requiredDataConnectors:
  - connectorId:  OfficeATP
    dataTypes:
    - SecurityAlert
  - 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:
  -  PrivilegeEscalation
relevantTechniques:
  - T1078
query: |
    let Alert_List= dynamic([
    "Phishing link click observed in Network Traffic",
    "Phish delivered due to an IP allow policy",
    "A potentially malicious URL click was detected",
    "High Risk Sign-in Observed in Network Traffic",
    "A user clicked through to a potentially malicious URL",
    "Suspicious network connection to AitM phishing site",
    "Messages containing malicious entity not removed after delivery",
    "Email messages containing malicious URL removed after delivery",
    "Email reported by user as malware or phish",
    "Phish delivered due to an ETR override",
    "Phish not zapped because ZAP is disabled"]);
    SecurityAlert
    | where AlertName in~ (Alert_List)
    //Findling Alerts which has the URL
    | where Entities has "url"
    //extracting Entities
    | extend Entities = parse_json(Entities)
    | mv-apply Entity = Entities on
        (
        where Entity.Type == 'url'
        | extend EntityUrl = tostring(Entity.Url)
        )
    | summarize
        Url=tostring(tolower(take_any(EntityUrl))),
        AlertTime= min(TimeGenerated),
        make_set(SystemAlertId, 100)
        by ProductName, AlertName
    // matching with 3rd party network logs and 3p Alerts
    | 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)
        | project
            3plogTime=TimeGenerated,
            DeviceVendor,
            DeviceProduct,
            Activity,
            DestinationHostName,
            DestinationIP,
            RequestURL=tostring(tolower(RequestURL)),
            MaliciousIP,
            SourceUserName=tostring(tolower(SourceUserName)),
            IndicatorThreatType,
            ThreatSeverity,
            ThreatConfidence,
            SourceUserID,
            SourceHostName)
        on $left.Url == $right.RequestURL
    // matching successful Login from suspicious IP
    | join kind=inner (SigninLogs
        //filtering the Successful Login
        | where ResultType == 0
        | project
            IPAddress,
            SourceSystem,
            SigniningTime= TimeGenerated,
            OperationName,
            ResultType,
            ResultDescription,
            AlternateSignInName,
            AppDisplayName,
            AuthenticationRequirement,
            ClientAppUsed,
            RiskState,
            RiskLevelDuringSignIn,
            UserPrincipalName=tostring(tolower(UserPrincipalName)),
            Name = tostring(split(UserPrincipalName, "@")[0]),
            UPNSuffix =tostring(split(UserPrincipalName, "@")[1]))
        on $left.DestinationIP == $right.IPAddress and $left.SourceUserName == $right.UserPrincipalName
    | where SigniningTime between ((AlertTime - 6h) .. (AlertTime + 6h)) and 3plogTime between ((AlertTime - 6h) .. (AlertTime + 6h))
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
    - entityType: Host
      fieldMappings:
      - identifier: FullName
        columnName: SourceSystem
    - entityType: URL
      fieldMappings:
      - identifier: Url
        columnName: RequestURL
kind: Scheduled
version: 1.0.5
metadata:
    source:
        kind: Community
    author:
        name: Arjun Trivedi
    support:
        tier: Community
    categories:
        domains: [ "Security - Threat Protection" ]

Stages and Predicates

Let binding: Alert_List

let Alert_List = dynamic([
"Phishing link click observed in Network Traffic",
"Phish delivered due to an IP allow policy",
"A potentially malicious URL click was detected",
"High Risk Sign-in Observed in Network Traffic",
"A user clicked through to a potentially malicious URL",
"Suspicious network connection to AitM phishing site",
"Messages containing malicious entity not removed after delivery",
"Email messages containing malicious URL removed after delivery",
"Email reported by user as malware or phish",
"Phish delivered due to an ETR override",
"Phish not zapped because ZAP is disabled"]);

Stage 1: source

SecurityAlert

Stage 2: where

| where AlertName in~ (Alert_List)

References Alert_List (defined above).

Stage 3: where

| where Entities has "url"

Stage 4: extend

| extend Entities = parse_json(Entities)

Stage 5: kusto:mv-apply

| mv-apply Entity = Entities on
    (
    where Entity.Type == 'url'
    | extend EntityUrl = tostring(Entity.Url)
    )

Stage 6: summarize

| summarize
    Url=tostring(tolower(take_any(EntityUrl))),
    AlertTime= min(TimeGenerated),
    make_set(SystemAlertId, 100)
    by ProductName, AlertName

Stage 7: 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)
    | project
        3plogTime=TimeGenerated,
        DeviceVendor,
        DeviceProduct,
        Activity,
        DestinationHostName,
        DestinationIP,
        RequestURL=tostring(tolower(RequestURL)),
        MaliciousIP,
        SourceUserName=tostring(tolower(SourceUserName)),
        IndicatorThreatType,
        ThreatSeverity,
        ThreatConfidence,
        SourceUserID,
        SourceHostName)
    on $left.Url == $right.RequestURL

Stage 8: join

| join kind=inner (SigninLogs
    | where ResultType == 0
    | project
        IPAddress,
        SourceSystem,
        SigniningTime= TimeGenerated,
        OperationName,
        ResultType,
        ResultDescription,
        AlternateSignInName,
        AppDisplayName,
        AuthenticationRequirement,
        ClientAppUsed,
        RiskState,
        RiskLevelDuringSignIn,
        UserPrincipalName=tostring(tolower(UserPrincipalName)),
        Name = tostring(split(UserPrincipalName, "@")[0]),
        UPNSuffix =tostring(split(UserPrincipalName, "@")[1]))
    on $left.DestinationIP == $right.IPAddress and $left.SourceUserName == $right.UserPrincipalName

Stage 9: where

| where SigniningTime between ((AlertTime - 6h) .. (AlertTime + 6h)) and 3plogTime between ((AlertTime - 6h) .. (AlertTime + 6h))

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.

FieldKindValues
AlertNamein
  • A potentially malicious URL click was detected
  • A user clicked through to a potentially malicious URL
  • Email messages containing malicious URL removed after delivery
  • Email reported by user as malware or phish
  • High Risk Sign-in Observed in Network Traffic
  • Messages containing malicious entity not removed after delivery
  • Phish delivered due to an ETR override
  • Phish delivered due to an IP allow policy
  • Phish not zapped because ZAP is disabled
  • Phishing link click observed in Network Traffic
  • Suspicious network connection to AitM phishing site
DeviceActionne
  • Block transforms: cased
DeviceProductstarts_with
  • FireWall
  • FortiGate
  • NSSWeblog
  • PAN
  • URL
  • VPN
DeviceVendormatch
  • Check Point
  • Fortinet
  • Palo Alto Networks
  • Zscaler
Entitiesmatch
  • url transforms: term
RequestURLis_not_null
  • (no value, null check)
ResultTypeeq
  • 0 transforms: cased
Typeeq
  • url transforms: cased

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.

FieldSource
AlertNamesummarize
AlertTimesummarize
ProductNamesummarize
Urlsummarize