The Email Was Genuine — The Conversation Had Been Hijacked
The supplier’s email was real. The conversation was real. Even the reply arrived in the same thread.
Only the bank details were different.
“Well,” Agent Foskett said, “at least the criminal had the courtesy to use Reply All.”
Was this a spoofed message, a malicious inbox rule, or a genuine account used by someone else?

The Hijacked Conversation
A legitimate thread can carry a fraudulent instruction when an account is compromised.
The invoice that arrived in the right conversation
A genuine message is not proof of a genuine sender
Reconstruct the investigation timeline
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
08:41 Unusual supplier account sign-in (requires validation) 08:48 Inbox rule created to hide finance replies 09:12 Existing invoice thread receives a new reply 09:14 Reply requests changed bank details 09:27 Finance flags the unexpected payment instruction 09:36 Security team preserves messages and audit evidence
1. Locate the suspicious email in Defender XDR
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
let startTime = datetime(2026-10-19T00:00:00Z);
let endTime = startTime + 2d;
EmailEvents
| where Timestamp between (startTime .. endTime)
| where SenderFromDomain =~ "example-supplier.com"
| where Subject has_any ("invoice", "payment", "bank details")
| project Timestamp, NetworkMessageId, InternetMessageId,
SenderFromAddress, SenderMailFromAddress,
RecipientEmailAddress, Subject, DeliveryAction,
EmailDirection, AuthenticationDetails
| order by Timestamp asc2. Follow the message and post-delivery activity
NetworkMessageId to correlate Defender XDR email tables. A delivered message may later be moved or remediated; this query shows available post-delivery actions, not whether the recipient acted on the instructions.- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
let suspiciousMessages =
EmailEvents
| where Timestamp > ago(14d)
| where SenderFromAddress =~ "accounts@example-supplier.com"
| project NetworkMessageId, MailTime=Timestamp,
RecipientEmailAddress, Subject;
suspiciousMessages
| join kind=leftouter (
EmailPostDeliveryEvents
| where Timestamp > ago(14d)
| project NetworkMessageId, ActionTime=Timestamp,
Action, ActionType
) on NetworkMessageId
| project MailTime, ActionTime, RecipientEmailAddress,
Subject, Action, ActionType, NetworkMessageId
| order by MailTime desc3. Look for suspicious mailbox activity
CloudAppEvents can surface relevant Exchange Online activity when that connector and event coverage are available in Defender XDR. Operation names and RawEventData fields vary: inspect your tenant’s schema and sample records before treating any event as conclusive.- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
CloudAppEvents
| where Timestamp > ago(14d)
| where Application == "Microsoft Exchange Online"
| where ActionType has_any (
"New-InboxRule", "Set-InboxRule", "Send", "MailItemsAccessed")
| project Timestamp, ActionType, AccountDisplayName,
AccountId, IPAddress, RawEventData
| order by Timestamp desc4. Examine sign-ins for the suspected account
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName =~ "accounts@example-supplier.com"
| project TimeGenerated, UserPrincipalName, IPAddress,
Location, AppDisplayName, ClientAppUsed,
ResultType, ConditionalAccessStatus,
AuthenticationRequirement, CorrelationId
| order by TimeGenerated asc5. Check Exchange mailbox audit records
OfficeActivity. The available operations depend on audit configuration, licensing and connector coverage.- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
OfficeActivity
| where TimeGenerated > ago(14d)
| where OfficeWorkload =~ "Exchange"
| where UserId =~ "accounts@example-supplier.com"
| where Operation has_any (
"New-InboxRule", "Set-InboxRule", "Send", "MailItemsAccessed")
| project TimeGenerated, Operation, UserId,
ClientIP, OfficeObjectId, Parameters
| order by TimeGenerated asc6. Hunt for other recipients of the same instruction
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
EmailEvents
| where Timestamp > ago(14d)
| where SenderFromAddress =~ "accounts@example-supplier.com"
| summarize FirstSeen=min(Timestamp), LastSeen=max(Timestamp),
Messages=count(),
Subjects=make_set(Subject, 10)
by RecipientEmailAddress, DeliveryAction
| order by Messages descWhat the logs can — and cannot — prove
The six-stage investigation
What the organisation should do next
Investigation findings
Related Agent Foskett investigations
Final thought
The logs already knew. The invoice approval process needed to ask.

