Detect Email Spoofing in Microsoft 365 with KQL
Sometimes the email looks legitimate. The sender appears familiar. The display name feels safe. The From address looks right.
But the trust behind it is fake.
This is where KQL becomes useful. At GEMXIT, we use Defender XDR hunting to validate whether a message was genuinely sent, badly authenticated, domain-spoofed, or simply trusted too quickly.
This sits right alongside stronger identity and access controls and practical security operations.
Briefing summary
This page focuses on practical KQL for spoofed email hunting using EmailEvents, AuthenticationDetails, sender alignment, authentication failures and message patterns that deserve closer investigation.
What this page is designed to solve
Start with the KQL people actually came for
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
EmailEvents | where Timestamp > ago(14d) | where AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail") | project Timestamp, SenderFromAddress, SenderFromDomain, SenderMailFromAddress, SenderMailFromDomain, RecipientEmailAddress, Subject, AuthenticationDetails, ThreatTypes, DeliveryAction | order by Timestamp desc
Check whether the visible sender and envelope sender disagree
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
EmailEvents | where Timestamp > ago(14d) | where SenderFromDomain != SenderMailFromDomain | project Timestamp, SenderFromAddress, SenderFromDomain, SenderMailFromAddress, SenderMailFromDomain, RecipientEmailAddress, Subject, AuthenticationDetails | order by Timestamp desc
Hunt for internal-looking or self-addressed spoofing patterns
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
let TargetDomain = "yourdomain.com"; EmailEvents | where Timestamp > ago(14d) | where SenderFromDomain =~ TargetDomain | where RecipientEmailAddress endswith TargetDomain | where SenderFromAddress =~ RecipientEmailAddress or AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail") | project Timestamp, SenderFromAddress, RecipientEmailAddress, Subject, AuthenticationDetails, DeliveryAction, UrlCount | order by Timestamp desc
Find repeated targeting patterns by sender or subject
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
EmailEvents | where Timestamp > ago(14d) | where AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail") | summarize MessageCount = count(), RecipientCount = dcount(RecipientEmailAddress) by SenderFromAddress, SenderFromDomain, Subject | where MessageCount >= 3 | order by MessageCount desc
Pivot after the email: who clicked and what happened next
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
let SuspiciousMessages = EmailEvents | where Timestamp > ago(14d) | where AuthenticationDetails has_any ("spf=fail", "dkim=fail", "dmarc=fail") | project NetworkMessageId, Timestamp, SenderFromAddress, RecipientEmailAddress, Subject; SuspiciousMessages | join kind=inner ( UrlClickEvents | where Timestamp > ago(14d) | project NetworkMessageId, ClickTime = Timestamp, AccountUpn, Url, ActionType ) on NetworkMessageId | order by ClickTime desc
What good email spoofing hunting looks like
Final thought
If you want help improving Microsoft email visibility, hunting, and operational response, 👉 review Microsoft security operations
Develop IT. Protect IT. GEMXIT PTY LTD | GEMXIT UK LTD