DMARC Failures Explained with KQL
DMARC failures can look scary at first glance, but they do not always mean a mailbox has been compromised.
This page explains how to investigate DMARC failures in Microsoft Defender XDR using EmailEvents, AuthenticationDetails, sender alignment, delivery actions and URL click pivots.
It is built for practical investigation: what failed, who received it, whether it was delivered, whether anyone clicked, and what to check next.
DMARC investigation summary
A focused Microsoft Defender KQL resource for investigating dmarc=fail results, sender alignment issues, delivery decisions and click activity.
Start with DMARC failures in EmailEvents
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromAddress,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction,
ThreatTypes,
NetworkMessageId
| order by Timestamp desc
How to read this query
SenderFromDomain ≠ SenderMailFromDomain
Repeated subject or sender = campaign signal
Forwarded mail can break authentication
Quarantined or blocked failures may already be contained
Check sign-ins for the affected user
Decide whether this is misconfiguration, spoofing or campaign activity
Compare DMARC, SPF and DKIM failures
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail")
| extend
SPF_Fail = iff(AuthenticationDetails has "spf=fail", "Yes", "No"),
DKIM_Fail = iff(AuthenticationDetails has "dkim=fail", "Yes", "No"),
DMARC_Fail = iff(AuthenticationDetails has "dmarc=fail", "Yes", "No")
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
SPF_Fail,
DKIM_Fail,
DMARC_Fail,
AuthenticationDetails,
DeliveryAction
| order by Timestamp desc
Find delivered messages where DMARC failed
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| where DeliveryAction !in~ ("Blocked", "Quarantined", "Junked")
| project
Timestamp,
SenderFromAddress,
SenderFromDomain,
SenderMailFromDomain,
RecipientEmailAddress,
Subject,
AuthenticationDetails,
DeliveryAction,
NetworkMessageId
| order by Timestamp desc
Group DMARC failures by sender domain
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| summarize
MessageCount = count(),
RecipientCount = dcount(RecipientEmailAddress),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by SenderFromDomain, SenderMailFromDomain, Subject, DeliveryAction
| order by MessageCount desc
Pivot DMARC failures to URL clicks
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
let DMARCFailures =
EmailEvents
| where Timestamp > ago(30d)
| where AuthenticationDetails has "dmarc=fail"
| project NetworkMessageId, EmailTime = Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, DeliveryAction;
DMARCFailures
| join kind=inner (
UrlClickEvents
| where Timestamp > ago(30d)
| project NetworkMessageId, ClickTime = Timestamp, AccountUpn, Url, ActionType
) on NetworkMessageId
| order by ClickTime desc
