DEV Community

MilkyWay008
MilkyWay008

Posted on

An email from your own address: spoof or hack? How to actually tell

The call every MSP dreads. A customer forwards you an email that shows their own address in the From line, and they're sure the account is compromised. You pull up message trace in the Exchange admin center, and there it is: user → user. Sent to himself. Now they're positive someone is inside their mailbox, and you have to figure out the truth without sounding like you're making excuses.

First thing to understand: message trace doesn't prove who sent a message. It shows the claimed sender. Microsoft's own message trace FAQ puts it bluntly: the trace tool uses the MAIL FROM value presented at the start of the SMTP conversation as the sender, regardless of what the message body shows. A spoofer forges MAIL FROM and the From: header to match the victim's domain, and the trace faithfully reports "user sent to user." It's not broken, it just can't see what you actually need.

The real story lives in the full message headers. Here's the path I walk every time.

1. Connect to Exchange Online PowerShell

Install-Module ExchangeOnlineManagement -Scope CurrentUser
Connect-ExchangeOnline
Enter fullscreen mode Exit fullscreen mode

2. Find the message in trace

The old Get-MessageTrace cmdlets are deprecated. Use the V2 versions. They search up to 90 days, 10 days per call:

$start = (Get-Date).AddDays(-2)
Get-MessageTraceV2 -SenderAddress user@domain.com -RecipientAddress user@domain.com -StartDate $start -EndDate (Get-Date)
Enter fullscreen mode Exit fullscreen mode

Grab the MessageTraceId from the output, then pull the details:

Get-MessageTraceDetailV2 -MessageTraceId <GUID> -RecipientAddress user@domain.com
Enter fullscreen mode Exit fullscreen mode

3. Pull the full headers

In Outlook desktop: open the message → File → Properties → Internet headers. In OWA: the three dots menu → View → View message details. Paste the whole thing into the Microsoft Header Analyzer (MHA), which parses the Received chain and the auth results for you.

4. Read the Authentication-results line

This is the money. Exchange Online Protection stamps an Authentication-results header (RFC 7001) on every inbound message. You're looking at the compauth value:

  • compauth=fail reason=601 means the sender claimed to be from one of your accepted domains but didn't come from Microsoft infrastructure. That's the exact self-to-self spoof signature.
  • spf=fail, dkim=fail, dmarc=fail → forged sender, full stop.
  • compauth=pass reason=100 with spf=pass, dkim=pass, dmarc=pass → the mail genuinely authenticated as your domain.

Also grab X-Forefront-Antispam-Report from the same headers. The CIP: field is the actual connecting IP, and PTR: is its reverse DNS. Both are useful for the next step. Full field reference lives in the anti-spam message headers doc.

5. The decision tree

Auth failed + the origin is an external IP → external spoof. No password reset needed. Block the source IP and domain, let spoof intelligence in the anti-phishing policy do its job, and keep DMARC at quarantine or reject. Customer goes home relieved.

Auth passed + the first external Received hop is a non-Microsoft IP → treat it as a real compromise. This mail genuinely came from your domain from outside your infrastructure. Disable the account, revoke sessions, reset the password, and force MFA re-registration. Then dig through the sign-in logs for foreign IPs and locations, and check inbox rules, forwarding, delegates, and consented apps while you're in there. Microsoft's compromise response guide is the canonical checklist.

Update-MgUser -UserId user@domain.com -AccountEnabled $false
Revoke-MgUserSignInSession -UserId user@domain.com
Enter fullscreen mode Exit fullscreen mode

Honest caveat: nothing in the trace proves origin, and that's the whole point. If you need certainty about a message, the headers are the source of truth. The trace is just the map that finds the message in the first place.

Top comments (0)