Adversaries may attempt to bypass Multi-Detector Objects (MDOs) by exploiting weaknesses in pre-delivery threat detection or post-delivery cleanup processes. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential evasion tactics and improve overall detection capabilities.
KQL Query
let _startTime = ago(30d);
let _endTime = now();
// Get all mailflow detected as clean at time of delivery
let EmailEventsClean = materialize(
EmailEvents
| where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
| where ThreatTypes !contains "Phish" and ThreatTypes !contains "Malware"
| project NetworkMessageId,ThreatTypes
);
// Get all mailflow detected as phish or malware at time of delivery
let EmailEventsThreats = materialize(
EmailEvents
| where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
| where ThreatTypes contains "Phish" or ThreatTypes contains "Malware"
| extend MDO_detection = parse_json(DetectionMethods)
| extend FirstDetection = iif(isempty(MDO_detection), "Clean", tostring(bag_keys(MDO_detection)[0]))
| extend FirstSubcategory = iif(FirstDetection != "Clean" and array_length(MDO_detection[FirstDetection]) > 0, strcat(FirstDetection, ": ", tostring(MDO_detection[FirstDetection][0])), "No Detection (clean)")
| project NetworkMessageId,FirstDetection,FirstSubcategory,MDO_detection,ThreatTypes
);
// Get all post delivery ZAP / Redelivery events, and arg_max them to ensure we have the latest verdict to work with for each
let EmailPostDeliveryFiltered = materialize(
EmailPostDeliveryEvents
| where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
| where ActionType in ("Malware ZAP","Phish ZAP","Redelivery")
| extend Key = strcat(NetworkMessageId , "-", RecipientEmailAddress)
| summarize arg_max(Timestamp, *) by Key
| project Action,ActionType,ActionResult,ThreatTypes,NetworkMessageId
);
// Optional - Get all admin submissions for malware or phish, so we can also count these in the miss bucket.
let CloudAppEventsFiltered = materialize(
CloudAppEvents
| where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
| where ActionType == "AdminSubmissionSubmitted"
| extend SubmissionType = tostring(parse_json(RawEventData).SubmissionType)
| extend NetworkMessageId = tostring(parse_json(RawEventData).ObjectId)
| where SubmissionType in ("1", "2")
| project SubmissionType,NetworkMessageId
);
// Get the number of threats caught in mailflow
let Mal_Phish_Mailflow = toscalar(
EmailEventsThreats
| summarize count()
);
// Get the number of threats caught in mailflow which turned out to be false positives (FPs) so we can correct the calculation
let FP_ZAP = toscalar(
EmailPostDeliveryFiltered
| where ThreatTypes !contains "Phish" and ThreatTypes !contains "Malware" and ActionType == "Redelivery"
| join kind=leftsemi (EmailEventsThreats) on NetworkMessageId
| summarize count()
);
// Get the number of threats successfully cleaned up post delivery, ignoring where administrative policy stopped action
let FN_ZAP_Successful = toscalar(
EmailPostDeliveryFiltered
| where ActionType in ("Malware ZAP","Phish ZAP") and ActionResult in ("Success","AdminPolicy")
| join kind=leftsemi (EmailEventsClean) on NetworkMessageId
| summarize count()
);
// Get the number of threats unsuccessfully cleaned up post delivery.
let FN_ZAP_Unsuccessful = toscalar(
EmailPostDeliveryFiltered
| where ActionType in ("Malware ZAP","Phish ZAP") and ActionResult !in ("Success","AdminPolicy")
| join kind=leftsemi (EmailEventsClean) on NetworkMessageId
| summarize count()
);
// Join the admin submissions to clean mailflow to find the additional miss
let FN_Admin_Submissions = toscalar(
CloudAppEventsFiltered
| join kind=rightsemi (EmailEventsClean) on NetworkMessageId
| summarize count()
);
// Print each result, and run the formula to calculate effectiveness at time of delivery and post delivery.
union withsource=Table
(print StatisticName="Mal/Phish Mailflow totals - Minus FPs", Value=toreal(Mal_Phish_Mailflow) - toreal(FP_ZAP)),
(print StatisticName="Admin Mal/Phish FNs Submitted", Value=toreal(FN_Admin_Submissions)),
(print StatisticName="Mal/Phish FPs Reverse Zapped", Value=toreal(FP_ZAP)),
(print StatisticName="Mal/Phish Successfully Zapped", Value=toreal(FN_ZAP_Successful)),
(print StatisticName="Mal/Phish UN-Successfully Zapped", Value=toreal(FN_ZAP_Unsuccessful)),
(print StatisticName="Effectiveness Post Delivery", Value=abs(round(((toreal(FN_Admin_Submissions)+toreal(FN_ZAP_Unsuccessful))/(toreal(Mal_Phish_Mailflow)+toreal(FN_ZAP_Successful)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_Admin_Submissions)-toreal(FP_ZAP))*100-100),2))),
(print StatisticName="Effectiveness Pre-Delivery", Value=abs(round(((toreal(FN_Admin_Submissions)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_ZAP_Successful))/(toreal(Mal_Phish_Mailflow)+toreal(FN_ZAP_Successful)+toreal(FN_ZAP_Unsuccessful)+toreal(FN_Admin_Submissions)-toreal(FP_ZAP))*100-100),2)))
| project StatisticName, Value
id: f2206cb7-62ca-4596-9d3a-544b61963799
name: Calculate overall MDO efficacy
description: |
This query helps calculate overall efficacy of MDO based on threats blocked pre-delivery, post-delivery cleanups, or were uncaught.
description-detailed: |
This query helps calculate overall efficacy of MDO based on threats blocked pre-delivery, items that were removed after delivery to the mailbox, or were uncaught because they were already deleted by an Admin/User etc.
Based on the query used within the Overview dashboard. Reference - https://learn.microsoft.com/en-gb/defender-office-365/reports-mdo-email-collaboration-dashboard#appendix-advanced-hunting-efficacy-query-in-defender-for-office-365-plan-2
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- EmailEvents
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
let _startTime = ago(30d);
let _endTime = now();
// Get all mailflow detected as clean at time of delivery
let EmailEventsClean = materialize(
EmailEvents
| where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
| where ThreatTypes !contains "Phish" and ThreatTypes !contains "Malware"
| project NetworkMessageId,ThreatTypes
);
// Get all mailflow detected as phish or malware at time of delivery
let EmailEventsThreats = materialize(
EmailEvents
| where Timestamp between (_startTime .. _endTime) and EmailDirection == "Inbound"
| where ThreatTypes contains "Phish" or ThreatTypes contains "Malware"
| extend MDO_detection = parse_json(DetectionMethods)
| extend FirstDetection = iif(isempty(MDO_detection), "Clean", tostring(bag_keys(MDO_detection)[0]))
| extend FirstSubcategory = iif(FirstDetection != "Clean" and array_length(MDO_detection[FirstDetection]) > 0, strcat(FirstDetection, ": ", tostring(MDO_detection[FirstDetection][0])), "No Detection (clean)")
| project NetworkMessageId,FirstDetection,FirstSubcategory,MDO_detection,ThreatTypes
);
// Get all post delivery ZAP / Redelivery events, and arg_max them to ensure we have the latest verdict to work with for each
let EmailPostDeliveryFiltered = materialize(
EmailPostDeliveryEvents
| where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
| where ActionType in ("Malware ZAP","Phish ZAP","Redelivery")
| extend Key = strcat(NetworkMessageId , "-", RecipientEmailAddress)
| summarize arg_max(Timestamp, *) by Key
| project Action,ActionType,ActionResult,ThreatTypes,NetworkMessageId
);
// Optional - Get all admin submissions for malware or phish, so we can also count these in the miss bucket.
let CloudAppEventsFiltered = materialize(
CloudAppEvents
| where Timestamp between (_startTime .. datetime_add('day', 7, _endTime))
| where ActionType == "AdminSubmissionSubmitted"
| extend SubmissionType = tostring(parse_json(RawEventData).Su
| Sentinel Table | Notes |
|---|---|
CloudAppEvents | Ensure this data connector is enabled |
EmailEvents | Ensure this data connector is enabled |
Scenario: Scheduled system cleanup tasks (e.g., Windows Disk Cleanup or third-party tools like CCleaner) that remove temporary files or logs
Filter/Exclusion: Exclude events related to known cleanup tools or tasks (e.g., ProcessName = "cleanmgr.exe" or ProcessName = "ccleaner.exe")
Scenario: Automated patching or update processes (e.g., Windows Update, Microsoft Endpoint Manager, or third-party tools like WSUS) that temporarily remove or modify files
Filter/Exclusion: Exclude events where the process is known to be part of an update or patching system (e.g., ProcessName = "wusa.exe" or ProcessName = "msiexec.exe")
Scenario: Regular log rotation or archival tasks (e.g., Logrotate on Linux, or tools like Splunk or ELK Stack) that delete or move log files
Filter/Exclusion: Exclude events related to log management tools or log rotation processes (e.g., ProcessName = "logrotate" or ProcessName = "splunkd")
Scenario: System maintenance tasks (e.g., disk defragmentation, chkdsk, or third-party tools like Defraggler) that modify file systems or delete temporary files
Filter/Exclusion: Exclude events associated with system maintenance utilities (e.g., ProcessName = "defrag.exe" or ProcessName = "chkdsk.exe")
Scenario: User-initiated file deletion or cleanup (e.g., a user manually deleting temporary files or using a tool like DiskDigger)
Filter/Exclusion: Exclude events where the user is known to be performing routine cleanup (e.g., User = "Domain\ITSupport" or ProcessName = "del.exe")