Detection rules › Kusto
Possible Phishing with CSL and Network Sessions
'This query looks for malicious URL clicks in phishing email recognized by MDO in correlation with CommonSecurityLogs(CSL) & NetworkSession events. If your workspace doesnt have one of the many data sources required for ASIM it may give informational error which can be safely ignored.'
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Initial Access | T1566 Phishing |
| Command & Control | T1102 Web Service |
Rule body kusto
id: 6c3a1258-bcdd-4fcd-b753-1a9bc826ce12
name: Possible Phishing with CSL and Network Sessions
description: |
'This query looks for malicious URL clicks in phishing email recognized by MDO in correlation with CommonSecurityLogs(CSL) & NetworkSession events.
If your workspace doesnt have one of the many data sources required for ASIM it may give informational error which can be safely ignored.'
severity: Medium
status: Available
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- AlertEvidence
- EmailEvents
- IdentityInfo
- DeviceEvents
- DeviceNetworkEvents
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
- connectorId: Fortinet
dataTypes:
- CommonSecurityLog
- connectorId: CheckPoint
dataTypes:
- CommonSecurityLog
- connectorId: PaloAltoNetworks
dataTypes:
- CommonSecurityLog
- connectorId: AWSS3
datatypes:
- AWSVPCFlow
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: MicrosoftSysmonForLinux
dataTypes:
- Syslog
- connectorId: AzureNSG
dataTypes:
- AzureDiagnostics
- connectorId: AzureMonitor(VMInsights)
dataTypes:
- VMConnection
- connectorId: AIVectraStream
dataTypes:
- VectraStream_CL
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- InitialAccess
- CommandAndControl
relevantTechniques:
- T1566
- T1102
tags:
- Schema: ASimNetworkSessions
SchemaVersion: 0.2.5
query: |
//SuspiciousUrlClicked
AlertEvidence
| where ServiceSource =~ "Microsoft Defender for Office 365"
| where EntityType =~ "Url"
| project AlertId, RemoteUrl
| join kind=inner (
AlertEvidence
| where ServiceSource =~ "Microsoft Defender for Office 365"
| where EntityType =~ "MailMessage"
| project AlertId, NetworkMessageId
)
on AlertId
| distinct RemoteUrl, NetworkMessageId
| join EmailEvents on NetworkMessageId
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress, RecipientObjectId
| join kind = inner IdentityInfo on $left.RecipientObjectId == $right.AccountObjectId
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress , RecipientObjectId, AccountSID
| join kind = inner
(DeviceEvents
| where ActionType =~ "BrowserLaunchedToOpenUrl"| where isnotempty(RemoteUrl)
| project UrlClickedByUserSid = RemoteUrl,
InitiatingProcessAccountSid, DeviceName, DeviceId, InitiatingProcessFileName,
InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain
)
on $left.AccountSID == $right.InitiatingProcessAccountSid and $left.RemoteUrl == $right.UrlClickedByUserSid
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress, RecipientObjectId,
AccountSID, UrlClickedByUserSid, DeviceName, DeviceId, InitiatingProcessFileName,
InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain
| join kind=inner
(
//Suspicious url clicked found in common security logs
CommonSecurityLog
| project TimeGenerated, DeviceVendor, DeviceProduct, DeviceAction, DestinationDnsDomain, DestinationIP, RequestURL, SourceIP, SourceHostName, RequestClientApplication
) on $left.RemoteUrl== $right.RequestURL
| join kind=inner
(
//Find the relevant network sessions
_Im_NetworkSession
| where isnotempty(DstIpAddr)
| where not(ipv4_is_private(DstIpAddr))
| project TimeGenerated, SrcIpAddr, SrcPortNumber, DstIpAddr, DstPortNumber, DstBytes, SrcBytes
) on $left.DestinationIP == $right.DstIpAddr //The relevant network session being projected
| summarize count() by TimeGenerated, RecipientEmailAddress, UrlClickedByUserSid, InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain,
DeviceName, InitiatingProcessFileName, DeviceProduct, DeviceAction, SourceIP, DestinationIP, RequestClientApplication
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
| extend RecipientEmailName = tostring(split(RecipientEmailAddress,'@',0)[0]), RecipientEmailUPNSuffix = tostring(split(RecipientEmailAddress,'@',1)[0])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: InitiatingProcessAccountUpn
- identifier: Name
columnName: InitiatingProcessAccountName
- identifier: UPNSuffix
columnName: InitiatingProcessAccountDomain
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: RecipientEmailAddress
- identifier: Name
columnName: RecipientEmailName
- identifier: UPNSuffix
columnName: RecipientEmailUPNSuffix
- entityType: Host
fieldMappings:
- identifier: FullName
columnName: DeviceName
- identifier: HostName
columnName: HostName
- identifier: DnsDomain
columnName: HostNameDomain
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: IP
fieldMappings:
- identifier: Address
columnName: DestinationIP
version: 1.1.2
kind: Scheduled
Stages and Predicates
Stage 1: source
AlertEvidence
Stage 2: where
| where ServiceSource =~ "Microsoft Defender for Office 365"
Stage 3: where
| where EntityType =~ "Url"
Stage 4: project
| project AlertId, RemoteUrl
Stage 5: join
| join kind=inner (
AlertEvidence
| where ServiceSource =~ "Microsoft Defender for Office 365"
| where EntityType =~ "MailMessage"
| project AlertId, NetworkMessageId
)
on AlertId
Stage 6: distinct
| distinct RemoteUrl, NetworkMessageId
Stage 7: join
| join EmailEvents on NetworkMessageId
Stage 8: distinct
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress, RecipientObjectId
Stage 9: join
| join kind = inner IdentityInfo on $left.RecipientObjectId == $right.AccountObjectId
Stage 10: distinct
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress , RecipientObjectId, AccountSID
Stage 11: join
| join kind = inner
(DeviceEvents
| where ActionType =~ "BrowserLaunchedToOpenUrl"| where isnotempty(RemoteUrl)
| project UrlClickedByUserSid = RemoteUrl,
InitiatingProcessAccountSid, DeviceName, DeviceId, InitiatingProcessFileName,
InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain
)
on $left.AccountSID == $right.InitiatingProcessAccountSid and $left.RemoteUrl == $right.UrlClickedByUserSid
Stage 12: distinct
| distinct RemoteUrl, NetworkMessageId, RecipientEmailAddress, RecipientObjectId,
AccountSID, UrlClickedByUserSid, DeviceName, DeviceId, InitiatingProcessFileName,
InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain
Stage 13: join
| join kind=inner
(
CommonSecurityLog
| project TimeGenerated, DeviceVendor, DeviceProduct, DeviceAction, DestinationDnsDomain, DestinationIP, RequestURL, SourceIP, SourceHostName, RequestClientApplication
) on $left.RemoteUrl== $right.RequestURL
Stage 14: join
| join kind=inner
(
_Im_NetworkSession
| where isnotempty(DstIpAddr)
| where not(ipv4_is_private(DstIpAddr))
| project TimeGenerated, SrcIpAddr, SrcPortNumber, DstIpAddr, DstPortNumber, DstBytes, SrcBytes
) on $left.DestinationIP == $right.DstIpAddr
Stage 15: summarize
| summarize count() by TimeGenerated, RecipientEmailAddress, UrlClickedByUserSid, InitiatingProcessAccountUpn, InitiatingProcessAccountName, InitiatingProcessAccountDomain,
DeviceName, InitiatingProcessFileName, DeviceProduct, DeviceAction, SourceIP, DestinationIP, RequestClientApplication
Stage 16: extend (3 consecutive steps)
| extend HostName = tostring(split(DeviceName, ".")[0]), DomainIndex = toint(indexof(DeviceName, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(DeviceName, DomainIndex + 1), DeviceName)
| extend RecipientEmailName = tostring(split(RecipientEmailAddress,'@',0)[0]), RecipientEmailUPNSuffix = tostring(split(RecipientEmailAddress,'@',1)[0])
Exclusions
Top-level NOT(...) conjuncts: predicates this rule actively suppresses.
| Field | Kind | Excluded values |
|---|---|---|
DstIpAddr | cidr_match | 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, 127.0.0.0/8 |
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 |
|---|---|---|
ActionType | eq |
|
DstIpAddr | is_not_null | |
EntityType | eq |
|
RemoteUrl | is_not_null | |
ServiceSource | eq |
|
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 |
|---|---|
DestinationIP | summarize |
DeviceAction | summarize |
DeviceName | summarize |
DeviceProduct | summarize |
InitiatingProcessAccountDomain | summarize |
InitiatingProcessAccountName | summarize |
InitiatingProcessAccountUpn | summarize |
InitiatingProcessFileName | summarize |
RecipientEmailAddress | summarize |
RequestClientApplication | summarize |
SourceIP | summarize |
TimeGenerated | summarize |
UrlClickedByUserSid | summarize |
DomainIndex | extend |
HostName | extend |
HostNameDomain | extend |
RecipientEmailName | extend |
RecipientEmailUPNSuffix | extend |