12.9 Campaign Tracking Across Waves

4-6 hours · Module 12

Campaign Tracking Across Waves

A single compromised account may be an isolated phishing success. Five waves over 72 hours is a campaign. This subsection teaches you to correlate infrastructure, TTPs, and timing across attack waves to build the complete campaign picture.


Identifying Wave 2: internal phishing from compromised accounts

The attacker uses j.morrison’s compromised mailbox to send phishing emails to internal users. These emails bypass Safe Links and Safe Attachments because they originate from a trusted internal sender.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Internal phishing  emails sent from compromised account to internal recipients
EmailEvents
| where TimeGenerated > datetime(2026-02-27T12:00:00Z)
| where SenderFromAddress == "j.morrison@northgateeng.com"
| where RecipientEmailAddress endswith "@northgateeng.com"
| where DeliveryAction == "Delivered"
| join kind=inner (
    EmailUrlInfo
    | where TimeGenerated > datetime(2026-02-27T12:00:00Z)
) on NetworkMessageId
| where UrlDomain !in ("northgateeng.com", "microsoft.com", "office.com", "sharepoint.com")
| project TimeGenerated, SenderFromAddress, RecipientEmailAddress, Subject, Url, UrlDomain
| order by TimeGenerated asc

What to look for: Emails from the compromised account containing URLs to domains that are NOT legitimate Microsoft or corporate domains. The subject line may mimic the original phishing campaign or use a new pretext.

Investigation decision point: If the attacker sent internal phishing: this is a campaign, not an isolated compromise. Every recipient is now a potential victim. Run the scoping queries from 11.3 for the new phishing domain(s). Check UrlClickEvents for clicks. Check SigninLogs for compromises.


Correlating attacker infrastructure across waves

Campaigns use multiple domains and IPs. Correlate them to build the infrastructure map.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// All attacker infrastructure  domains and IPs used across the campaign
let AttackerIPs = SigninLogs
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where UserPrincipalName in ("j.morrison@northgateeng.com",
    "s.chen@northgateeng.com", "a.patel@northgateeng.com")
| where IPAddress !in ("192.0.2.10", "192.0.2.15")  // Exclude known corporate
| distinct IPAddress;
let PhishingDomains = EmailUrlInfo
| where TimeGenerated > datetime(2026-02-26T09:00:00Z)
| where UrlDomain has_any ("secure-portal-verify", "login-microsoftonline", "sharepoint-secure")
| distinct UrlDomain;
print AttackerIPs = toscalar(AttackerIPs | summarize make_set(IPAddress)),
    PhishingDomains = toscalar(PhishingDomains | summarize make_set(UrlDomain))

Build the campaign infrastructure table:

WaveDatePhishing DomainAttacker IP(s)Compromised Users
127 Feb 08:45secure-portal-verify.com203.0.113.47j.morrison
227 Feb 14:00microsoftonline-auth.com203.0.113.47, 203.0.113.52s.chen, a.patel
328 Feb 03:00(no new phishing — persistence)203.0.113.52s.chen, a.patel (existing)
428 Feb 09:00sharepoint-secure-docs.com203.0.113.89r.williams, m.thompson, + 1
528 Feb 16:00(no new phishing — BEC attempt)203.0.113.52a.patel (BEC from existing compromise)

This table is a critical artifact in the IR report (11.11) and drives the hardening recommendations (11.12).

Subsection artifact: The campaign infrastructure correlation queries and the campaign infrastructure table template. These form the campaign tracking section of your AiTM investigation playbook.


Knowledge check


Attacker infrastructure evolution across waves

Sophisticated campaigns rotate infrastructure between waves to evade IP-based blocking. Track the evolution:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Infrastructure rotation: unique IPs and domains per wave
let Wave1Start = datetime(2026-02-27T08:00:00Z);
let Wave2Start = datetime(2026-02-27T14:00:00Z);
let Wave3Start = datetime(2026-02-28T03:00:00Z);
let Wave4Start = datetime(2026-02-28T09:00:00Z);
let Wave5Start = datetime(2026-02-28T16:00:00Z);
SigninLogs
| where TimeGenerated > Wave1Start
| where IPAddress !in ("192.0.2.10", "192.0.2.15")
| where ResultType == "0"
| extend Wave = case(
    TimeGenerated between(Wave1Start .. Wave2Start), "Wave 1",
    TimeGenerated between(Wave2Start .. Wave3Start), "Wave 2",
    TimeGenerated between(Wave3Start .. Wave4Start), "Wave 3",
    TimeGenerated between(Wave4Start .. Wave5Start), "Wave 4",
    TimeGenerated > Wave5Start, "Wave 5",
    "Unknown")
| summarize
    UniqueIPs = dcount(IPAddress),
    IPs = make_set(IPAddress, 10),
    UniqueUsers = dcount(UserPrincipalName),
    Users = make_set(UserPrincipalName, 10)
    by Wave
| order by Wave asc

What this reveals: If the attacker uses the same IP across all waves: a single block stops the campaign. If different IPs per wave: the attacker is rotating infrastructure and IP-based blocking alone is insufficient — you need token binding or FIDO2 to prevent the authentication bypass itself.

TI sharing: The campaign infrastructure table (IPs, domains, email addresses) should be shared with your ISAC, industry peers, and threat intelligence sharing platforms. The IOCs help other organisations detect the same campaign before the attacker pivots to new infrastructure.

Try it yourself

Using the Northgate Engineering scenario data, build the complete campaign infrastructure table: one row per wave with phishing domain, attacker IPs, compromised users, and the technique used (external phishing, internal phishing, or persistence access). This table is a key artifact in the IR report — practice building it now so the structure is familiar during a real incident.

What you should produce

A 5-row table (one per wave) showing the progression: Wave 1 external phishing from a single domain, Wave 2 internal phishing from the compromised mailbox with a new domain, Wave 3 persistence activity (no new phishing), Wave 4 internal phishing from two compromised mailboxes with a third domain, Wave 5 BEC attempt. The table tells the story of the campaign's evolution.

Check your understanding

1. Wave 2 emails were sent from j.morrison's compromised mailbox to internal users. Why might these emails bypass Safe Links?

Internal emails are treated as trusted by default. Safe Links evaluates external email URLs more aggressively than internal ones. An email from j.morrison@northgateeng.com to colleagues is from a trusted internal sender — the URL may not be scanned with the same rigour as an external email, or the Safe Links policy may not apply to internal-to-internal email (depending on policy configuration). This is why internal phishing from compromised accounts is so effective.
Safe Links was disabled
The URLs were different from Wave 1
Internal emails are never scanned