This hypothesis targets adversaries leveraging phishing campaigns (T1566) that achieve high user engagement through malicious URL clicks, indicating successful initial access or credential harvesting. Proactively hunting for these high-interaction campaigns allows the SOC to identify compromised endpoints and lateral movement paths before attackers fully establish a foothold in the Azure environment.
//This query ranks malicious inbound email campaigns from the last 30 days by recipient URL clicks, not by size.
//A small campaign that reached mailboxes and got clicked is realised risk; a large one blocked at delivery is not,
//so ranking by engagement inverts the triage order compared with ranking by message volume.
//Campaigns carrying QR codes are flagged, since the URL is not visible to the user or to link inspection.
//Phishing simulation and SecOps mailbox traffic are excluded so internal exercises do not inflate the ranking.
let ClicksByCluster = UrlClickEvents
| where Timestamp > ago(30d)
| join kind=inner (EmailEvents | where Timestamp > ago(30d) | where EmailClusterId > 0 | distinct NetworkMessageId, EmailClusterId) on NetworkMessageId
| summarize UrlClicks = count(), ClickingUsers = dcount(AccountUpn) by EmailClusterId;
let QrClusters = EmailUrlInfo
| where Timestamp > ago(30d)
| where UrlLocation == "QRCode"
| join kind=inner (EmailEvents | where Timestamp > ago(30d) | where EmailClusterId > 0 | distinct NetworkMessageId, EmailClusterId) on NetworkMessageId
| summarize QrMessages = dcount(NetworkMessageId) by EmailClusterId;
EmailEvents
| where Timestamp > ago(30d)
| where EmailDirection == "Inbound" and isnotempty(ThreatTypes) and EmailClusterId > 0
| where OrgLevelPolicy !in ("Phishing simulation", "SecOps Mailbox")
| extend Key = strcat(NetworkMessageId, "-", RecipientEmailAddress)
| summarize arg_max(Timestamp, *) by Key
| summarize Messages = count(),
Recipients = dcount(RecipientEmailAddress),
SenderDomains = dcount(SenderFromDomain),
SampleSenderDomains = make_set(SenderFromDomain, 5),
ThreatTypesRaw = make_set(ThreatTypes, 50),
SampleSubjects = make_set(Subject, 5),
FirstSeen = min(Timestamp),
LastSeen = max(Timestamp)
by EmailClusterId
| join kind=inner (ClicksByCluster) on EmailClusterId
| where UrlClicks > 0
| join kind=leftouter (QrClusters) on EmailClusterId
| extend CarriedQrCode = iif(coalesce(QrMessages, 0) > 0, "QR code", "")
| extend ThreatMix = array_sort_asc(set_union(split(strcat_array(ThreatTypesRaw, ", "), ", "), dynamic([])))
| top 20 by UrlClicks
| project ['Email Cluster ID']=EmailClusterId, ['URL Clicks']=UrlClicks, ['Clicking Users']=ClickingUsers,
['Messages']=Messages,
['Recipients']=Recipients, ['Sender Domains']=SenderDomains,
['Sample Sender Domains']=SampleSenderDomains, ['Threat Mix']=ThreatMix,
['QR Code']=CarriedQrCode, ['Sample Subjects']=SampleSubjects,
['First Seen']=FirstSeen, ['Last Seen']=LastSeen
id: 28ce9655-f35f-4733-af43-8266b39a87f1
name: Malicious Email Campaigns by Recipient URL Clicks
description: |
This query ranks malicious inbound email campaigns by the number of URL clicks they attracted and by how many distinct users clicked, rather than by campaign size.
description-detailed: |
Microsoft Defender for Office 365 groups related malicious messages into campaigns through EmailClusterId. This query ranks those campaigns over the last 30 days by the number of recipient URL clicks they attracted, using Advanced hunting in Microsoft Defender XDR, and flags campaigns that carried QR codes. Both the total number of clicks and the number of distinct users behind them are returned, so one repeat clicker is not mistaken for broad engagement. Ranking by clicks rather than by message volume answers a different question from campaign size: a small campaign that reached mailboxes and was clicked represents realised risk, whereas a large campaign that was blocked at delivery did not. For triage that inverts the priority order, because the campaigns worth investigating first are the ones users actually engaged with. Messages are de-duplicated to the latest record per message and recipient, and phishing simulation and SecOps mailbox traffic are excluded so internal exercises do not inflate the ranking.
requiredDataConnectors:
- connectorId: MicrosoftThreatProtection
dataTypes:
- EmailEvents
- EmailUrlInfo
- UrlClickEvents
tactics:
- InitialAccess
relevantTechniques:
- T1566
query: |
//This query ranks malicious inbound email campaigns from the last 30 days by recipient URL clicks, not by size.
//A small campaign that reached mailboxes and got clicked is realised risk; a large one blocked at delivery is not,
//so ranking by engagement inverts the triage order compared with ranking by message volume.
//Campaigns carrying QR codes are flagged, since the URL is not visible to the user or to link inspection.
//Phishing simulation and SecOps mailbox traffic are excluded so internal exercises do not inflate the ranking.
let ClicksByCluster = UrlClickEvents
| where Timestamp > ago(30d)
| join kind=inner (EmailEvents | where Timestamp > ago(30d) | where EmailClusterId > 0 | distinct NetworkMessageId, EmailClusterId) on NetworkMessageId
| summarize UrlClicks = count(), ClickingUsers = dcount(AccountUpn) by EmailClusterId;
let QrClusters = EmailUrlInfo
| where Timestamp > ago(30d)
| where UrlLocation == "QRCode"
| join kind=inner (EmailEvents | where Timestamp > ago(30d) | where EmailClusterId > 0 | distinct NetworkMessageId, EmailClusterId) on NetworkMessageId
| summarize QrMessages = dcount(NetworkMessageId) by EmailClusterId;
EmailEvents
| where Timestamp > ago(30d)
| where EmailDirection == "Inbound" and isnotempty(ThreatTypes) and EmailClusterId > 0
| where OrgLevelPolicy !in ("Phishing simulation", "SecOps Mailbox")
| extend Key = strcat(NetworkMessageId, "-", Recipient
| Sentinel Table | Notes |
|---|---|
EmailEvents | Ensure this data connector is enabled |
EmailUrlInfo | Ensure this data connector is enabled |
UrlClickEvents | Ensure this data connector is enabled |
Scenario: A marketing team sends a “Q3 Product Launch” newsletter containing links to the company’s public-facing landing page or demo environment. Employees click these links to review content or test features, generating high click volumes from distinct users.
*.marketing.example.com, *.landing.example.com) or specific path patterns like /campaigns/ or /news/ when the sender is from a verified marketing distribution list.Scenario: IT Operations distributes a mandatory “Security Awareness Training” or “Password Reset” notification via email. The link points to an internal LMS (e.g., Cornerstone, Workday) or identity provider (e.g., Azure AD, Okta) login page. High click counts are expected as all targeted users must complete the action.
login.microsoftonline.com, sso.example.com, lms.example.com) or paths indicating administrative tasks like /reset, /training, or /onboarding.Scenario: A new software tool (e.g., Jira, Confluence, or a custom internal app) is rolled out, and users receive an invitation email with a “Join Workspace” or “Verify Account” link. This link often redirects to an external authentication service or a specific setup endpoint, causing a spike in clicks from distinct users during the rollout period.
*.atlassian.net, *.zendesk.com) or specific query parameters like ?invite=, ?token=, or ?setup= when the sender is from a verified IT or vendor distribution list.Scenario: A finance or