11.9 MITRE ATT&CK-Driven Hunting

14-18 hours · Module 11

MITRE ATT&CK-Driven Hunting

Introduction

Required role: Microsoft Sentinel Reader (minimum for hunting queries). Sentinel Contributor for bookmark and hunt management.

MITRE ATT&CK provides a structured catalogue of adversary techniques organised by tactic (the attacker’s goal) and technique (the method). Using ATT&CK to drive hunting means: systematically identifying techniques that are relevant to your environment, checking which have detection coverage (Module 10.11), and hunting for the ones that do not. This ensures hunting is not random — it methodically closes the visibility gaps that analytics rules leave open.


The ATT&CK-driven hunting workflow

Step 1: Identify relevant techniques. Not every ATT&CK technique applies to your environment. Filter by: your platform (Windows, Azure AD/Entra ID, M365, Linux), the tactics most commonly used against your industry (check industry threat reports), and the techniques used by threat groups known to target similar organisations.

Step 2: Check detection coverage. Navigate to Sentinel → MITRE ATT&CK. The blade shows which techniques have active analytics rules (green) and which do not (grey). Cross-reference with your hunting query coverage — some uncovered techniques may have hunting queries but no automated rules.

Step 3: Prioritise uncovered techniques. Not every uncovered technique needs a hunt. Prioritise: techniques in the “Initial Access” and “Persistence” tactics (these are the entry points and footholds — if you miss them, the attacker stays), techniques reported in recent threat intelligence for your industry, and techniques with high impact if undetected (privilege escalation, data exfiltration).

Step 4: Hunt for each prioritised technique. Formulate a hypothesis: “Technique T1078.004 (Valid Accounts: Cloud Accounts) may have been used to access our environment via compromised cloud credentials.” Write KQL to search for evidence of the technique. Execute and analyse.

Step 5: Update coverage. If the hunt finds evidence → create an incident AND an analytics rule. If the hunt finds nothing → document the hunt. Either way, the technique has been “covered” by hunting. Update your coverage tracking.


Hunting queries mapped to high-priority ATT&CK techniques

T1078.004 — Valid Accounts: Cloud Accounts. Hunt for compromised cloud credentials by looking for sign-ins from unusual infrastructure.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Hunt: cloud account access from suspicious infrastructure
SigninLogs
| where TimeGenerated > ago(30d)
| where ResultType == "0"
| where not(ipv4_is_private(IPAddress))
| extend ASN = tostring(AutonomousSystemNumber)
| summarize
    UniqueUsers = dcount(UserPrincipalName),
    Users = make_set(UserPrincipalName, 10),
    SigninCount = count()
    by ASN, IPAddress
| where UniqueUsers > 3  // Same IP accessing multiple accounts
| order by UniqueUsers desc

Multiple users signing in from the same external IP suggests: either a corporate VPN exit point (expected) or an attacker using compromised credentials for multiple accounts (investigate). Cross-reference with your known corporate IPs.

T1098.003 — Account Manipulation: Additional Cloud Roles. Hunt for unexpected role assignments that grant persistent privileged access.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Hunt: privileged role assignments to unexpected users
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName has "Add member to role"
| where Result == "success"
| extend RoleName = tostring(TargetResources[0].modifiedProperties[1].newValue)
| extend TargetUser = tostring(TargetResources[0].userPrincipalName)
| extend Assigner = tostring(InitiatedBy.user.userPrincipalName)
| where RoleName has_any ("Global Admin", "Exchange Admin",
    "Security Admin", "Privileged Role Admin")
| project TimeGenerated, Assigner, TargetUser, RoleName
| order by TimeGenerated desc

T1136.003 — Create Account: Cloud Account. Hunt for new accounts created outside normal HR provisioning.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// Hunt: new account creation not from HR provisioning system
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName == "Add user"
| where Result == "success"
| extend Creator = tostring(InitiatedBy.user.userPrincipalName)
| extend NewUser = tostring(TargetResources[0].userPrincipalName)
| where Creator !in (
    _GetWatchlist('HRProvisioningAccounts') | project SearchKey)
| project TimeGenerated, Creator, NewUser,
    IPAddress = tostring(InitiatedBy.user.ipAddress)

T1114.003 — Email Collection: Email Forwarding Rule. Hunt for mailbox rules forwarding to external addresses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Hunt: inbox rules forwarding to non-corporate domains
CloudAppEvents
| where TimeGenerated > ago(30d)
| where ActionType in ("New-InboxRule", "Set-InboxRule")
| extend RuleData = parse_json(RawEventData)
| extend ForwardTo = tostring(RuleData.Parameters
    | mv-expand RuleData.Parameters
    | where tostring(RuleData.Parameters.Name) in ("ForwardTo", "RedirectTo")
    | project tostring(RuleData.Parameters.Value))
| where isnotempty(ForwardTo)
| where ForwardTo !endswith "@northgateeng.com"
| extend Creator = tostring(RuleData.UserId)
| project TimeGenerated, Creator, ForwardTo,
    IPAddress = tostring(RuleData.ClientIP)

Additional high-value technique hunts

T1550.001 — Use Alternate Authentication Material: Application Access Token. Hunt for OAuth token abuse — applications accessing resources with unusual patterns.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Hunt: OAuth applications accessing unusual numbers of mailboxes
CloudAppEvents
| where TimeGenerated > ago(30d)
| where Application == "Microsoft Exchange Online"
| where ActionType in ("MailItemsAccessed", "Send")
| extend AppId = tostring(parse_json(RawEventData).AppId)
| where isnotempty(AppId) and AppId != "00000000-0000-0000-0000-000000000000"
| summarize
    MailboxesAccessed = dcount(AccountObjectId),
    Mailboxes = make_set(AccountDisplayName, 10),
    ActionCount = count()
    by AppId
| where MailboxesAccessed > 5
| order by MailboxesAccessed desc

An application accessing many mailboxes may be a legitimate service (backup, eDiscovery) or a malicious OAuth app harvesting email. Cross-reference the AppId with known legitimate applications.

T1059.001 — Command and Scripting Interpreter: PowerShell. Hunt for encoded PowerShell — a common obfuscation technique.

1
2
3
4
5
6
7
8
9
// Hunt: encoded PowerShell execution
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where FileName in ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("-enc", "-EncodedCommand",
    "FromBase64String", "Convert]::FromBase64", "-e ")
| project TimeGenerated, DeviceName, AccountName,
    ProcessCommandLine, InitiatingProcessFileName
| order by TimeGenerated desc

Encoded PowerShell is used by both attackers (to hide malicious commands) and some legitimate tools (deployment scripts, configuration management). The hunter’s job: distinguish between them by examining the decoded command content and the context (who ran it, on which device, at what time).

T1003.006 — OS Credential Dumping: DCSync. Hunt for DCSync attacks that replicate directory data from domain controllers.

1
2
3
4
5
6
7
// Hunt: DCSync replication requests from non-DC machines
IdentityDirectoryEvents
| where TimeGenerated > ago(30d)
| where ActionType == "Directory Services Replication"
| where DestinationDeviceName !has "DC"  // Not from a domain controller
| project TimeGenerated, AccountName, DestinationDeviceName,
    TargetDeviceName, Application

DCSync requests from non-domain-controller machines are almost always malicious — an attacker using mimikatz or a similar tool to extract credentials from Active Directory.


M365-specific ATT&CK technique hunts

These hunts target techniques specifically relevant to M365 environments — the primary environment this course covers.

T1621 — Multi-Factor Authentication Request Generation (MFA fatigue). Hunt for users receiving many MFA challenges without successfully authenticating.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Hunt: MFA fatigue patterns
SigninLogs
| where TimeGenerated > ago(14d)
| where ResultType in ("50074", "500121", "50076")  // MFA-related result codes
| summarize
    MFAChallenges = count(),
    UniqueIPs = dcount(IPAddress),
    IPs = make_set(IPAddress, 5),
    TimeSpan = max(TimeGenerated) - min(TimeGenerated)
    by UserPrincipalName, bin(TimeGenerated, 1h)
| where MFAChallenges > 5
| order by MFAChallenges desc

More than 5 MFA challenges in a single hour for the same user is highly suspicious — either an attacker pushing MFA prompts hoping the user will approve, or a misconfigured application repeatedly triggering MFA. The hunter checks: did the user eventually approve (indicating successful fatigue attack)? Were the IPs external or internal?

T1098.002 — Account Manipulation: Additional Email Delegate Permissions. Hunt for mailbox delegation changes that grant an attacker access to read another user’s email.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// Hunt: mailbox delegation changes
CloudAppEvents
| where TimeGenerated > ago(30d)
| where ActionType in ("Add-MailboxPermission", "Set-Mailbox")
| extend Details = parse_json(RawEventData)
| extend TargetMailbox = tostring(Details.ObjectId)
| extend GrantedTo = tostring(Details.Parameters
    | mv-expand Details.Parameters
    | where tostring(Details.Parameters.Name) == "User"
    | project tostring(Details.Parameters.Value))
| extend AccessRights = tostring(Details.Parameters
    | mv-expand Details.Parameters
    | where tostring(Details.Parameters.Name) == "AccessRights"
    | project tostring(Details.Parameters.Value))
| where AccessRights has_any ("FullAccess", "ReadPermission")
| project TimeGenerated, TargetMailbox, GrantedTo, AccessRights,
    Performer = tostring(Details.UserId)

An attacker who compromises one mailbox may grant themselves delegate access to other mailboxes — avoiding the need to compromise additional accounts.

T1537 — Transfer Data to Cloud Account. Hunt for data transfer to personal cloud storage.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Hunt: data uploads to consumer cloud services
DeviceNetworkEvents
| where TimeGenerated > ago(14d)
| where RemoteUrl has_any ("dropbox.com", "drive.google.com",
    "onedrive.live.com", "icloud.com", "mega.nz", "wetransfer.com")
| summarize
    UploadCount = count(),
    UniqueDevices = dcount(DeviceName),
    Devices = make_set(DeviceName, 5),
    TotalBytes = sum(SentBytes)
    by AccountName, RemoteUrl
| where UploadCount > 10 or TotalBytes > 100000000  // >100MB
| order by TotalBytes desc

Cross-reference with the departing employees watchlist and UEBA anomaly data for the strongest insider threat signal.


M365-specific technique hunts

Standard MITRE ATT&CK hunting guidance focuses on endpoint and network techniques. These hunts target cloud-native techniques specific to M365 environments.

T1621 — Multi-Factor Authentication Request Generation (MFA Fatigue).

1
2
3
4
5
6
7
// Hunt: repeated MFA push notifications followed by approval
SigninLogs
| where TimeGenerated > ago(14d)
| where ResultType in ("50074", "500121")
| summarize DenialCount = count() by UserPrincipalName, bin(TimeGenerated, 1h)
| where DenialCount > 5
| project UserPrincipalName, DenialCount, TimeWindow = TimeGenerated

T1098.002 — Account Manipulation: Additional Email Delegate Permissions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Hunt: mailbox delegate permissions granted outside IT provisioning
CloudAppEvents
| where TimeGenerated > ago(30d)
| where ActionType == "Add-MailboxPermission"
| extend Details = parse_json(RawEventData)
| extend TargetMailbox = tostring(Details.ObjectId)
| extend GrantedTo = tostring(Details.Parameters
    | mv-expand Details.Parameters
    | where tostring(Details.Parameters.Name) == "User"
    | project tostring(Details.Parameters.Value))
| extend AccessRights = tostring(Details.Parameters
    | mv-expand Details.Parameters
    | where tostring(Details.Parameters.Name) == "AccessRights"
    | project tostring(Details.Parameters.Value))
| extend Performer = tostring(Details.UserId)
| where GrantedTo != Performer  // Someone granting access to someone else
| where AccessRights has_any ("FullAccess", "ReadPermission")
| project TimeGenerated, TargetMailbox, GrantedTo, AccessRights, Performer,
    ClientIP = tostring(Details.ClientIP)

T1059.001 — Command and Scripting Interpreter: PowerShell.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Hunt: encoded PowerShell execution on Defender-managed endpoints
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("-enc", "-EncodedCommand", "-e ",
    "FromBase64String", "Invoke-Expression", "IEX", "Invoke-WebRequest",
    "downloadstring", "Net.WebClient")
| extend CommandLength = strlen(ProcessCommandLine)
| where CommandLength > 500  // Encoded commands are typically long
| project TimeGenerated, DeviceName, AccountName,
    ProcessCommandLine = substring(ProcessCommandLine, 0, 200),  // Truncate for readability
    CommandLength, SHA256
| order by CommandLength desc

Encoded PowerShell from a corporate endpoint warrants investigation. Legitimate automation scripts are rarely encoded. Attacker scripts are frequently encoded to evade detection.

T1136.001 — Create Account: Local Account.

1
2
3
4
5
6
7
8
9
// Hunt: local account creation on Defender-managed devices
DeviceEvents
| where TimeGenerated > ago(30d)
| where ActionType == "UserAccountCreated"
| extend AccountCreated = tostring(AdditionalFields)
| project TimeGenerated, DeviceName,
    CreatedBy = InitiatingProcessAccountName,
    AccountCreated, InitiatingProcessCommandLine
| order by TimeGenerated desc

Local account creation on a corporate device is unusual — most organisations provision accounts centrally. A new local admin account on a device may be an attacker establishing persistence.


Tactic-based hunting rotation

Rotate through MITRE ATT&CK tactics systematically to ensure comprehensive hunting coverage over time.

Month 1: Initial Access + Credential Access. Hunt for: phishing (T1566), valid accounts (T1078), brute force (T1110), MFA fatigue (T1621). These are the entry points — if you catch the attacker here, the attack is stopped before any damage occurs.

Month 2: Persistence + Privilege Escalation. Hunt for: account manipulation (T1098), scheduled tasks (T1053), OAuth application consent (T1550), role assignment (T1098.003). These are the footholds — if you find persistence, the attacker is already in but can still be evicted.

Month 3: Lateral Movement + Collection. Hunt for: remote services (T1021), token replay (T1550), email forwarding rules (T1114), data staging (T1074). These are the actions — if you find collection activity, data may already be exfiltrated.

Month 4: Execution + Defense Evasion. Hunt for: command interpreters (T1059), encoded commands, log clearing (T1070), indicator removal. These are the tools and techniques — finding them reveals the attacker’s operational methods.

Repeat the rotation quarterly. Each cycle, the hunting queries improve (refined from previous hunts), new techniques are added (from threat intelligence), and the coverage tracker shows steady improvement.


ATT&CK coverage gap analysis query

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Identify techniques with zero detection AND zero hunting coverage
// Requires: analytics rule MITRE mappings + hunt log tracking
// This is a conceptual query  adapt for your tracking method
let DetectedTechniques = SecurityIncident
| where TimeGenerated > ago(90d)
| extend Techniques = parse_json(tostring(AdditionalData)).techniques
| mv-expand Techniques
| distinct tostring(Techniques);
let HuntedTechniques = HuntingBookmark
| where TimeGenerated > ago(90d)
| extend Tags = parse_json(Tags)
| mv-expand Tags
| where tostring(Tags) startswith "T"  // MITRE technique IDs in tags
| distinct tostring(Tags);
let AllPriorityTechniques = datatable(Technique:string) [
    "T1566", "T1078", "T1110", "T1621",  // Initial Access
    "T1098", "T1136", "T1053", "T1550",  // Persistence
    "T1021", "T1114", "T1059", "T1003"   // Lateral/Execution
];
AllPriorityTechniques
| join kind=leftanti DetectedTechniques on $left.Technique == $right.Column1
| join kind=leftanti HuntedTechniques on $left.Technique == $right.Column1
| project Technique, Status = "⚠️ NO COVERAGE — neither detected nor hunted"

This query surfaces your blind spots — techniques with zero coverage from either detection or hunting. Every technique in this list is an opportunity for an attacker to operate undetected.


Building the ATT&CK hunting coverage tracker

Maintain a tracker that records which techniques have been hunted, when, and with what result.

TechniqueLast HuntedResultAnalytics Rule?Next Hunt
T1078.004 Cloud Accounts2026-03-22No findings✓ Yes2026-06-22
T1098.003 Additional Roles2026-03-15Benign✓ Yes2026-06-15
T1136.003 Create Cloud AcctNot hunted✗ NoPriority
T1114.003 Email Forwarding2026-03-10Threat confirmed✓ Yes (new)2026-04-10

Techniques marked “Not hunted” with no analytics rule are your highest-priority blind spots. Techniques with recent hunts and analytics rules have the strongest coverage. Rotate through the tracker quarterly — hunt each priority technique at least once per quarter.

Try it yourself

Navigate to Sentinel → MITRE ATT&CK. Identify 3 techniques that are relevant to your environment but have no analytics rule coverage (grey/uncovered). For one technique, write and execute a hunting query. Document the result in a hunt record. If you find suspicious activity, create a bookmark. If you find nothing, document the negative finding. This is one iteration of ATT&CK-driven hunting.

What you should observe

The MITRE ATT&CK blade highlights your coverage gaps visually. The hunting query either returns suspicious activity (investigate further) or nothing (document as "no findings for this period"). Either way, you have now actively hunted for that technique — the coverage tracker reflects this activity.


Knowledge check

Compliance mapping

NIST CSF: DE.AE-1 (Baseline of operations established), PR.DS-1 (Data-at-rest is protected). ISO 27001: A.8.15 (Logging), A.8.16 (Monitoring activities). SOC 2: CC7.2 (Monitor system components). Every configuration in this subsection contributes to the logging and monitoring controls that auditors verify.


Check your understanding

1. The MITRE ATT&CK blade shows T1136.003 (Create Cloud Account) has no analytics rule and has never been hunted. What do you do?

Hunt for it. Write a KQL query that searches for cloud account creation events not originating from your HR provisioning system. If the hunt finds unauthorised account creation: promote to incident and create an analytics rule. If the hunt finds nothing: document the negative finding and create an analytics rule to detect this technique automatically going forward. Either way, the technique moves from "uncovered" to "covered."
Ignore it — not every technique needs coverage
Wait for Content Hub to release a rule
Add it to next year's roadmap