Chapter 17

Business Email Compromise Playbook

Detecting inbox rule manipulation, OAuth app abuse, mail forwarding persistence, and fraudulent wire transfer — and the 72-hour window to recover stolen funds.

Scenario

Your CFO's assistant calls the SOC: the CFO's email account was used to approve a $340,000 wire transfer yesterday. The CFO says they didn't approve it. In BEC incidents, time is the adversary: the FBI's financial fraud unit has a 72-hour window to place a "financial freeze" on fraudulent wire transfers before the money is moved to overseas accounts. Every hour you spend figuring out what happened is an hour of recovery time lost. This playbook handles both tracks simultaneously: the fraud recovery track and the technical containment track.

The Two Simultaneous Tracks

  BEC Response: Parallel Tracks
  ═══════════════════════════════════════════════════════════════════

  TRACK 1: FINANCIAL FRAUD RECOVERY (time-critical)
  ─────────────────────────────────────────────────
  T+0:00  Confirm fraudulent transfer with Finance
  T+0:30  Call your bank's fraud department — request wire recall
  T+1:00  File FBI IC3 complaint at ic3.gov
  T+2:00  Call FBI local field office financial crimes unit
          (not just the IC3 website — direct call speeds freeze request)
  T+24h   72-hour window — any freeze must be in place by this point
          The FBI can file SWIFT network alerts and coordinate with
          destination bank to freeze funds

  TRACK 2: TECHNICAL INVESTIGATION AND CONTAINMENT
  ─────────────────────────────────────────────────
  T+0:00  Disable compromised account (if confirmed)
  T+0:30  Revoke all OAuth tokens and active sessions
  T+1:00  Audit inbox rules — find any forwarding rules
  T+2:00  Audit OAuth app consents — find any attacker-registered apps
  T+4:00  Full mailbox audit — what was accessed, forwarded, deleted?
  T+6:00  Scope expansion — are other accounts affected?

Mailbox Forensics

PowerShellbec-mailbox-forensics.ps1
# Exchange Online / O365 BEC Investigation
# Requires ExchangeOnlineManagement module and audit logs enabled

Connect-ExchangeOnline

$victimUPN = "jsmith@corp.com"

# 1. Check for inbox rules — attackers create rules to forward emails to external accounts
#    or delete specific alerts (security alerts, finance notifications)
Get-InboxRule -Mailbox $victimUPN | Select-Object Name, Description,
    ForwardTo, ForwardAsAttachmentTo, RedirectTo, DeleteMessage, Enabled |
    Format-List

# 2. Look at recently modified/created inbox rules
Get-InboxRule -Mailbox $victimUPN -IncludeHidden | Format-List *

# 3. Check OAuth app consents for this user
# (via Azure AD portal or Graph API)
# az ad app list --filter "appId eq 'specific-app-id'" or review Entra ID → Enterprise Apps

# 4. Audit log: what was accessed?
# (Requires Unified Audit Log enabled and E3/E5 license for full detail)
$endDate   = Get-Date
$startDate = $endDate.AddDays(-30)

Search-UnifiedAuditLog -StartDate $startDate -EndDate $endDate `
    -UserIds $victimUPN -RecordType ExchangeItem `
    -ResultSize 1000 | Where-Object {
    $_.Operations -in @("MailItemsAccessed","MessageBind","Create","Update","MoveToDeletedItems")
} | Select-Object CreationDate, Operations, ClientIP, AuditData |
    Sort-Object CreationDate | Format-Table
PowerShellbec-sign-in-audit.ps1
# Azure AD sign-in logs for the compromised account
# Look for: suspicious IPs, impossible travel, unfamiliar device/browser

Connect-MgGraph -Scopes "AuditLog.Read.All"

$filter = "userPrincipalName eq 'jsmith@corp.com' and createdDateTime gt 2026-08-01T00:00:00Z"
Get-MgAuditLogSignIn -Filter $filter -Top 200 |
    Select-Object CreatedDateTime, IpAddress, Location, ClientAppUsed,
                  IsInteractive, ConditionalAccessStatus,
                  @{N="Status"; E={ $_.Status.ErrorCode }} |
    Sort-Object CreatedDateTime |
    Format-Table

# Red flags in output:
# - IPs from unexpected countries
# - Non-interactive sign-ins (refresh token reuse) after password reset
# - ClientAppUsed = "Other clients" or "IMAP4" (legacy auth, bypasses MFA)
# - IsInteractive = False (OAuth token replay — doesn't require new MFA)

BEC Indicators of Compromise

IndicatorWhat it meansWhere to find it
Inbox rule forwarding to external addressAttacker is receiving a copy of all incoming emailGet-InboxRule output
Inbox rule deleting specific subjectsAttacker deleting security alerts or fraud notifications to avoid victim noticingGet-InboxRule — DeleteMessage=True
OAuth app granted full mailbox accessAttacker registered an OAuth app with Mail.ReadWrite permissionAzure AD → Enterprise Apps → Permissions
Non-interactive sign-ins from new IP after MFA resetRefresh token still alive despite password resetAzure AD Sign-In logs
Legacy authentication protocol (IMAP/POP)Attacker using legacy auth to bypass MFA — a persistent access methodAzure AD Sign-In logs: ClientAppUsed = IMAP/POP
Email sent from account at unusual hoursAttacker actively using the account to conduct fraudExchange mail sent items or audit log

BEC Containment Steps

PowerShellbec-containment.ps1
# BEC containment — in order

# 1. Disable the account immediately
Set-MgUser -UserId "jsmith@corp.com" -AccountEnabled $false

# 2. Revoke all active sessions and refresh tokens
Invoke-MgUserInvalidateAllRefreshToken -UserId "jsmith@corp.com"

# 3. Remove malicious inbox rules
Get-InboxRule -Mailbox "jsmith@corp.com" |
    Where-Object { $_.ForwardTo -or $_.ForwardAsAttachmentTo -or $_.RedirectTo } |
    ForEach-Object {
        Write-Host "Removing rule: $($_.Name) — ForwardTo: $($_.ForwardTo)"
        Remove-InboxRule -Mailbox "jsmith@corp.com" -Identity $_.RuleIdentity -Confirm:$false
    }

# 4. Block legacy authentication (prevents IMAP/POP bypass of MFA)
# Via Conditional Access: Block legacy auth for all users, or this user specifically
# Via Azure AD → Security → Conditional Access → New Policy
# Conditions: Client Apps → Exchange ActiveSync + Other Clients
# Access Control: Block

# 5. Reset password and require MFA re-registration
Set-MgUserPassword -UserId "jsmith@corp.com" `
    -PasswordProfile @{ Password = (New-Guid).ToString() + "Aa1!"; ForceChangePasswordNextSignIn = $true }

Q & A

Q: The wire transfer was sent to a domestic bank account. Does the 72-hour window still apply?

Yes — domestic wires can still be recalled. Call your bank's wire fraud department immediately, referencing the date, amount, and destination account. Request a "wire recall" (also called "return of funds"). For domestic transfers, the bank may be able to contact the receiving bank directly and freeze the funds if they haven't been moved. File an IC3 complaint as well — even for domestic fraud, FBI involvement can speed up the process with the receiving bank. Also: contact your cyber insurance carrier immediately — many policies cover BEC losses, and the insurer's panel may have relationships with fraud recovery specialists who work with banks directly.

Q: You remove the attacker's inbox forwarding rule, reset the password, and revoke tokens. The next morning, the attacker is still in the mailbox. How?

Three likely paths: (1) The attacker has a legitimate OAuth app consent that survived the token revocation — OAuth app permissions persist even after token revocation unless you also revoke the app consent. Check Azure AD → Enterprise Apps → [user] → Permissions and revoke any unfamiliar app consents. (2) The attacker created a hidden inbox rule that you didn't find — re-run Get-InboxRule with -IncludeHidden, and also check via REST API which sometimes shows rules the cmdlet doesn't. (3) Another account in the organization was also compromised and the attacker is now accessing the mailbox via a delegation or shared mailbox permission. Check mailbox permissions: Get-MailboxPermission -Identity "jsmith@corp.com".