Detecting AiTM Phishing via 3rd-Party Network events in Unified Security Operations Platform
Microsoft Security has been evolving from individual security products – such as endpoint, email, identity, and app – to XDR (Extended Detection and Response) solution, and it also offers a cloud-native SIEM solution, Microsoft Sentinel. Despite having these two strong security backbones, we have made tremendous progress by unifying the SIEM and XDR experience into a single platform called the Unified Security Operations Platform. Thanks to that, this platform provides comprehensive visibility, investigation, and response capabilities across endpoints, hybrid identities, emails, collaboration tools, cloud apps, cloud workloads, and data.
Regarding Advanced Hunting capability, there are now no boundaries for threat hunting. Security analysts can access various tables across Microsoft Defender XDR and Microsoft Sentinel. However, with the introduction of the unified hunting experience, “SecurityAlert” table, which previously contained data from Microsoft security solutions and was available in Microsoft Sentinel, is no longer present in Advanced Hunting. Instead, Advanced Hunting now utilizes “AlertInfo“ and “AlertEvidence” tables.
Hunting Adversary-in-the-Middle (AiTM) attacks is a great example of gaining additional insights through advanced hunting. AiTM attacks use sophisticated tactics, including the creation of fraudulent sites that intercept user login credentials. This allows attackers to hijack sign-in sessions and bypass authentication protections. Even users with Multifactor Authentication (MFA) enabled can fall victim to this method. A Unified Security Operations Platform not only provides out-of-the-box (OOTB) detection alerts but also includes attack disruption capabilities to stop ongoing attacks, thanks to its correlation mechanisms and various signals from Microsoft Defender XDR. Although a Unified Security Operations Platform provides a significant number of AiTM detections, you may still want visibility into how third-party network activity and network detections correlate with first-party logs, such as Entra ID sign-in events and AiTM-related URL click actions. Therefore, at this time, we would like to provide two queries.
Hunting AiTM Phishing Events in a Unified Security Operations Platform
These two KQL queries utilize four data tables. The first is CommonSecurityLog table, which stores 3rd-party network logs from sources such as ‘Palo Alto Networks,’ ‘Fortinet,’ ‘Check Point,’ and ‘Zscaler,’ available through Microsoft Sentinel. The second is SigninLogs table, which contains Microsoft Entra ID sign-in event data from Microsoft Sentinel. Finally, AlertEvidence and AlertInfo tables contain detection data from Microsoft Defender XDR. By using join operations and binding common keys across four different data tables within a single platform, we can detect suspicious AiTM-related activities in Advanced Hunting, a Unified Security Operations Platforms.
Prerequisite :
1) These KQL queries should be executed within the Unified Security Operations Platform environment, rather than in Microsoft Defender XDR or Microsoft Sentinel portal. For details on integration, please refer to Connect Microsoft Sentinel to Microsoft Defender XDR – Microsoft Defender XDR | Microsoft Learn
2) To run these queries, Azure Monitor, CommonSecurityLog table (‘Palo Alto Networks,’ ‘Fortinet,’ ‘Check Point,’ and ‘Zscaler’) must is required. For details on the table, please refer to Azure Monitor Logs reference – CommonSecurityLog | Microsoft Learn
Phishing Link Clicks in Network Traffic
Description: This rule is designed to identify successful phishing link clicks by users and the subsequent network activity from non-Microsoft network devices.
How it works: It identifies phishing-related alerts in Microsoft Defender XDR and matches them with 3rd party network device logs such as Firewalls instead non Microsoft devices. It aims to detect successful phishing link clicks followed by suspicious network activity.
// Define a list of alert titles that we are interested in
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”]);
// Filter AlertInfo for relevant alerts within the past 10 days from Defender for Office 365
AlertInfo
| where TimeGenerated > ago(5d)
| where DetectionSource == “Microsoft Defender for Office 365”
| where Title has_any (Alert_List)
// Join with AlertEvidence to get additional evidence details
| join kind=inner (
AlertEvidence
| where TimeGenerated > ago(5d)
| where DetectionSource == “Microsoft Defender for Office 365”
| where EntityType in (“Url”, “User”)
) on AlertId
// Parse the JSON field AdditionalFields to extract entities
| extend Entities = parse_json(AdditionalFields)
| mv-apply Entity = Entities on (
where Entity.Type in (‘account’, ‘url’)
| extend
// Assign entity properties based on type
EntityUPN = iff(Entities.Type == ‘account’, strcat(Entities.Name, “@”, Entities.UPNSuffix), Entities.UserPrincipalName),
“”,
EntityUrl = iff(Entities.Type == ‘url’, tostring(Entities.Url), “”)
)
// Extract the domain from the URL if it’s not empty
| extend DomainFromUrl = iff(isnotempty(EntityUrl), tostring(parse_url(EntityUrl).Host), “”)
// Summarize to create sets of UPNs and URLs grouped by AlertId and TimeGenerated
| summarize UserPrincipalNames = make_set(EntityUPN), Urls = make_set(EntityUrl) by AlertId, TimeGenerated
// Expand the sets to have individual rows for each UPN and URL
| mv-expand Urls
| mv-expand UserPrincipalNames
// Filter out empty URLs and UPNs
| where isnotempty(Urls)
| where isnotempty(UserPrincipalNames)
// Parse URL into its components
| extend Url = tostring(Urls)
| extend Domain = tostring(parse_url(Url).Host), Path = tostring(parse_url(Url).Path)
// Project relevant columns
| project AlertTime = TimeGenerated, AlertId, UserPrincipalName = UserPrincipalNames, Url, Domain, Path, tostring(parse_url(Url))
// Join with CommonSecurityLog for related network activity
| join kind=inner (
CommonSecurityLog
//| where TimeGenerated > ago(5d)
| where DeviceAction != “Block”
| where DeviceProduct has_any (“FortiGate”, “PAN”, “VPN”, “FireWall”, “NSSWeblog”, “URL”)
| where isnotempty(RequestURL)
| where isnotempty(SourceUserName)
| extend RequestURL = tostring(tolower(RequestURL))
| project
LogTime = TimeGenerated,
DeviceVendor,
DeviceProduct,
Activity,
DestinationHostName,
DestinationIP,
Domain = tostring(parse_url(RequestURL).Host),
RequestPath = tostring(parse_url(RequestURL).Path),
MaliciousIP,
UserName = tostring(split(SourceUserName, “@”)[0]),
UPNSuffix = tostring(split(SourceUserName, “@”)[1]),
SourceUserName,
IndicatorThreatType,
ThreatSeverity,
AdditionalExtensions,
ThreatConfidence
) on Domain // Join on Domain for matching records
| where RequestPath has Path
Correlating M365D Alerts with Non-Microsoft Network Device Activity
Description: This rule correlates Microsoft Defender XDR phishing-related alerts with sign-in activities on non-Microsoft network devices, especially when users connect to phishing URLs.
How it works: It correlates Microsoft 365 Defender alerts with network logs from devices like Palo Alto Networks, Fortinet, Check Point, and Zscaler. It focuses on cases where users connect to phishing URLs from these devices and subsequently make successful sign-in attempts.
// Define a list of alert titles that we are interested in
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”]);
// Filter AlertInfo for relevant alerts within the past 10 days from Defender for Office 365
AlertInfo
| where TimeGenerated > ago(5d)
| where DetectionSource == “Microsoft Defender for Office 365”
| where Title has_any (Alert_List)
// Join with AlertEvidence to get additional evidence details
| join kind=inner (
AlertEvidence
| where TimeGenerated > ago(5d)
| where DetectionSource == “Microsoft Defender for Office 365”
| where EntityType in (“Url”, “User”)
) on AlertId
// Parse the JSON field AdditionalFields to extract entities
| extend Entities = parse_json(AdditionalFields)
| mv-apply Entity = Entities on (
where Entity.Type in (‘account’, ‘url’)
| extend
// Assign entity properties based on type
EntityUPN = iff(Entities.Type == ‘account’, strcat(Entities.Name, “@”, Entities.UPNSuffix), Entities.UserPrincipalName),
“”,
EntityUrl = iff(Entities.Type == ‘url’, tostring(Entities.Url), “”)
)
// Extract the domain from the URL if it’s not empty
| extend DomainFromUrl = iff(isnotempty(EntityUrl), tostring(parse_url(EntityUrl).Host), “”)
// Summarize to create sets of UPNs and URLs grouped by AlertId and TimeGenerated
| summarize UserPrincipalNames = make_set(EntityUPN), Urls = make_set(EntityUrl) by AlertId, TimeGenerated
// Expand the sets to have individual rows for each UPN and URL
| mv-expand Urls
| mv-expand UserPrincipalNames
// Filter out empty URLs and UPNs
| where isnotempty(Urls)
| where isnotempty(UserPrincipalNames)
// Parse URL into its components
| extend Url = tostring(Urls)
| extend Domain = tostring(parse_url(Url).Host), Path = tostring(parse_url(Url).Path)
| extend AlertTime= TimeGenerated
// matching with 3rd party network logs and 3p Alerts
| join kind= inner (CommonSecurityLog
| where TimeGenerated > ago(5d)
| 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 TimeGenerated > ago(5d)
| 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))
Microsoft Tech Community – Latest Blogs –Read More