Advanced ⏱ 120 min 📋 10 Steps Lab 4 of 4

Enterprise Threat Hunting with MITRE ATT&CK

Build a proactive threat hunting program in Microsoft Sentinel. craft KQL hunting queries mapped to MITRE ATT&CK techniques, leverage bookmarks and livestream, create coverage workbooks, and establish a systematic hunting cadence.

📋 Overview

About This Lab

In this advanced lab you will build threat hunting queries mapped to MITRE ATT&CK techniques across Initial Access, Persistence, and Lateral Movement tactics. You will use Sentinel bookmarks to capture evidence, livestream queries for real-time detection, promote hunting results to analytics rules, and build a workbook that visualizes your ATT&CK coverage. closing gaps and strengthening your organization’s security posture.

🏢 Enterprise Use Case

A government agency with 10,000 users has been targeted by APT groups conducting low-and-slow intrusions. Their SOC operates reactively. investigating alerts only after they fire. A recent audit revealed that only 30% of MITRE ATT&CK techniques are covered by existing analytics rules, leaving critical gaps in Initial Access, Execution, Persistence, Privilege Escalation, and Lateral Movement. Leadership has mandated a systematic threat hunting program to reduce mean dwell time from 21 days to under 48 hours.

🎯 What You Will Learn

  1. Map Sentinel analytics rules to MITRE ATT&CK techniques
  2. Audit your current detection coverage using the ATT&CK blade
  3. Write KQL hunting queries for Initial Access (T1078, T1566)
  4. Write KQL hunting queries for Persistence (T1136, T1098)
  5. Write KQL hunting queries for Lateral Movement (T1021, T1550)
  6. Use bookmarks to preserve and annotate hunting evidence
  7. Configure livestream queries for continuous real-time monitoring
  8. Promote successful hunts into scheduled analytics rules
  9. Build a custom hunting coverage workbook with JSON templates
  10. Establish a recurring threat hunting cadence and documentation process

🔑 Why This Matters

The median dwell time for advanced threat actors remains 10·21 days globally, with some APTs persisting for months undetected. Reactive detection alone is insufficient. attackers actively evade analytics rules. Proactive threat hunting closes blind spots by hypothesis-driven investigation of telemetry that has not triggered alerts. Organizations with mature hunting programs detect breaches 60% faster and reduce breach costs by an average of $1.1M (IBM Cost of a Data Breach 2024). MITRE ATT&CK provides the common language to measure and systematically expand your coverage.

⚙️ Prerequisites

  • Active Azure subscription with Microsoft Sentinel enabled on a Log Analytics workspace
  • Microsoft Sentinel Contributor role (minimum) on the workspace
  • At least one data connector active (Azure AD / Entra ID sign-in logs recommended)
  • Familiarity with KQL (completed Sentinel Labs 1·3 or equivalent)
  • Understanding of MITRE ATT&CK framework concepts (tactics, techniques, sub-techniques)
  • Access to Microsoft Sentinel Hunting blade and Workbooks blade
⚠️ Important: This lab uses realistic hunting queries against Azure AD sign-in and audit logs. Ensure your workspace has at least 7 days of SigninLogs, AuditLogs, and SecurityEvent data ingested for the queries to return meaningful results.

Step 1 · Understand the MITRE ATT&CK Framework and Sentinel Integration

MITRE ATT&CK is a globally accessible knowledge base of adversary tactics and techniques based on real-world observations. Microsoft Sentinel natively integrates ATT&CK by allowing you to tag analytics rules, hunting queries, and incidents with specific technique IDs.

Key Concepts

  • Tactics . the "why" (e.g., Initial Access, Persistence, Lateral Movement)
  • Techniques . the "how" (e.g., T1078 Valid Accounts, T1566 Phishing)
  • Sub-techniques . granular variants (e.g., T1566.001 Spearphishing Attachment)
  • Data Sources . the telemetry needed (e.g., Authentication logs, Process creation)

Navigate to the Sentinel MITRE ATT&CK Blade

  1. Open the Azure PortalMicrosoft Sentinel
  2. Select your workspace
  3. In the left menu under Threat management, click MITRE ATT&CK
  4. The matrix view shows all Enterprise tactics and techniques. Colored cells indicate coverage by active analytics rules, hunting queries, or both.
💡 Pro Tip: Use the Simulated toggle in the ATT&CK blade to see what coverage would look like if you activated all available content hub solutions. this helps identify quick wins for closing coverage gaps.

Step 2 · Audit Current Detection Coverage with the MITRE ATT&CK Blade

Before hunting, you need a baseline of your current detection posture. Use this KQL query against the Sentinel API metadata to enumerate which ATT&CK techniques have active coverage:

// Enumerate analytics rules and their MITRE ATT&CK technique mappings
// PURPOSE: Audit which ATT&CK techniques are covered by active analytics rules
// WHY: Gaps in technique coverage = blind spots attackers can exploit undetected
let ActiveRules = _GetWatchlist('AnalyticsRuleTemplates')
    | where Status == "Enabled";            // Only active (not disabled) rules
//
// Alternative approach: Query SecurityAlert table for technique tags
// This works even without the watchlist - uses historical alert data
SecurityAlert
| where TimeGenerated > ago(30d)            // Last 30 days of alert history
| extend Techniques = extract_all(@"(T\d{4}(\.\d{3})?)", tostring(ExtendedProperties))
  // Regex extracts MITRE technique IDs: T1078, T1110.003, etc.
  // Matches both techniques (T####) and sub-techniques (T####.###)
| mv-expand Technique = Techniques          // Expand arrays - one row per technique per alert
| summarize
    RuleCount = dcount(AlertName),           // How many distinct rules map to this technique
    Rules = make_set(AlertName)              // Which rules cover this technique
    by tostring(Technique)                   // Group by MITRE technique ID
| sort by RuleCount desc
// Expected output: Ranked list of techniques by detection coverage
// Techniques at the top = well covered; missing techniques = priority gaps
// ACTION: Cross-reference with MITRE ATT&CK Navigator to visualize coverage

Manual Audit Steps

  1. In the MITRE ATT&CK blade, filter by Active rules only
  2. Export the matrix view or take note of uncovered tactics
  3. Focus priority on: Initial Access, Execution, Persistence, Privilege Escalation, Lateral Movement
  4. Document baseline coverage percentage per tactic in a tracking spreadsheet
💡 Pro Tip: The MITRE ATT&CK blade counts both analytics rules and hunting queries as coverage. When building your baseline, distinguish between automated detection (analytics rules) and manual hunting queries to accurately measure your residual risk.

Step 3 · Build Hunting Queries for Initial Access (T1078 & T1566)

Navigate to Microsoft SentinelHunting+ New Query. Create the following hunting queries for Initial Access techniques.

T1078 . Valid Accounts: Anomalous Sign-in Patterns

Hunt for compromised valid accounts by detecting sign-ins from unusual locations, impossible travel, or sign-ins to rarely-accessed applications:

// T1078 - Valid Accounts: Detect sign-ins from new countries not seen in past 14 days
// PURPOSE: Hunt for compromised accounts being used from unfamiliar geolocations
// WHY: Attackers often operate from different countries than the legitimate user
// MITRE ATT&CK: T1078 - Valid Accounts (Initial Access)
//
// Step 1: Build a baseline of known countries per user over the past 14 days
let LookbackPeriod = 14d;    // Baseline window - defines "normal" behavior
let HuntingWindow = 1d;       // Hunt window - look for anomalies in the last 24 hours
let KnownCountries = SigninLogs
    | where TimeGenerated between (ago(LookbackPeriod) .. ago(HuntingWindow))
    | where ResultType == 0                    // Only successful sign-ins for baseline
    | summarize by UserPrincipalName, Country = LocationDetails.countryOrRegion;
//
// Step 2: Find recent sign-ins from countries NOT in the user’s baseline
SigninLogs
| where TimeGenerated > ago(HuntingWindow)     // Only the last 24 hours
| where ResultType == 0                        // Only successful sign-ins (actual access)
| extend Country = tostring(LocationDetails.countryOrRegion)
| join kind=leftanti KnownCountries            // leftanti = keep rows with NO match
    on UserPrincipalName, Country              // Match on user + country combination
//
// Step 3: Summarize anomalous sign-ins per user+country
| summarize
    FirstSeen = min(TimeGenerated),            // When the new-country sign-in started
    LastSeen = max(TimeGenerated),             // Most recent sign-in from new country
    SigninCount = count(),                     // How many sign-ins from this new country
    Apps = make_set(AppDisplayName, 5),        // Which apps were accessed
    IPs = make_set(IPAddress, 5)               // Source IPs from the new country
    by UserPrincipalName, Country
| where SigninCount >= 1                       // Any new-country sign-in is suspicious
| sort by SigninCount desc
// Expected output: Users signing in from countries they’ve never used before
// ACTION: Verify with user; if unauthorized → disable account, revoke sessions
// FALSE POSITIVES: Business travelers, newly deployed VPN in a new region

T1566 . Phishing: Suspicious Email Followed by Risky Sign-in

Correlate email delivery events with risky sign-in behavior within a short time window:

// T1566 - Phishing: Correlate suspicious email delivery with risky sign-in behavior
// PURPOSE: Detect phishing emails that led to actual account compromise
// WHY: Email alone is noisy; correlating with risky sign-ins finds SUCCESSFUL phishing
// MITRE ATT&CK: T1566 - Phishing (Initial Access)
let TimeWindow = 2h;          // Max time between email delivery and suspicious sign-in
EmailEvents
| where TimeGenerated > ago(1d)                        // Last 24 hours of email data
| where ThreatTypes has "Phish" or DeliveryAction == "Delivered"  // Phishing or delivered emails
| extend RecipientUPN = tolower(RecipientEmailAddress) // Normalize for case-insensitive join
| join kind=inner (                                    // inner join = only matching pairs
    SigninLogs
    | where TimeGenerated > ago(1d)
    | where RiskLevelDuringSignIn in ("high", "medium") // Only risky sign-ins
    | extend SigninUPN = tolower(UserPrincipalName)     // Normalize to match email recipient
    | project SigninTime = TimeGenerated, SigninUPN, IPAddress,
              RiskLevel = RiskLevelDuringSignIn, AppDisplayName
) on $left.RecipientUPN == $right.SigninUPN             // Join on user identity
| where SigninTime between (TimeGenerated .. (TimeGenerated + TimeWindow))
  // Sign-in must occur AFTER email delivery, within the 2-hour window
| project
    EmailTime = TimeGenerated, SigninTime,
    User = RecipientUPN, Subject, SenderFromAddress,
    IPAddress, RiskLevel, AppDisplayName
// Expected output: Users who received phishing email AND had a risky sign-in within 2h
// ACTION: HIGH PRIORITY - likely successful phishing; disable account, investigate email content

Saving the Hunting Query

  1. Click + New Query in the Hunting blade
  2. Paste the KQL, set a descriptive Name (e.g., "Hunt-T1078-NewCountrySignin")
  3. Under MITRE ATT&CK, select Initial AccessT1078 Valid Accounts
  4. Set Entity mappings: Account → UserPrincipalName, IP → IPAddress
  5. Click Create
💡 Pro Tip: Name your hunting queries with a consistent naming convention like Hunt-T[ID]-[ShortDescription]. This makes it easy to filter and correlate in the Hunting blade and in workbook visualizations.

Step 4 · Build Hunting Queries for Persistence (T1136 & T1098)

T1136 . Create Account: Unauthorized Account Creation

Hunt for new accounts created outside of standard provisioning workflows, especially accounts created by non-admin users or service principals:

// T1136 - Create Account: Detect unauthorized account creation outside business hours
// PURPOSE: Hunt for rogue accounts created outside standard provisioning workflows
// WHY: Attackers create backdoor accounts for persistence during off-hours to avoid detection
// MITRE ATT&CK: T1136 - Create Account (Persistence)
let ProvisioningHoursStart = 8; // 8 AM - start of normal business/provisioning window
let ProvisioningHoursEnd = 18;  // 6 PM - end of normal business/provisioning window
AuditLogs
| where TimeGenerated > ago(7d)                       // Look back 7 days
| where OperationName in ("Add user", "Add member to group", "Add service principal")
  // These operations create identities that could be used for persistence
| extend
    InitiatedBy = tostring(InitiatedBy.user.userPrincipalName),    // Human initiator
    InitiatedByApp = tostring(InitiatedBy.app.displayName),        // App/automation initiator
    TargetUser = tostring(TargetResources[0].displayName),         // New account display name
    TargetUPN = tostring(TargetResources[0].userPrincipalName),    // New account UPN
    HourOfDay = hourofday(TimeGenerated),                          // Hour (0-23) of creation
    DayOfWeek = dayofweek(TimeGenerated)                           // Day of week (0=Sun)
| where HourOfDay !between (ProvisioningHoursStart .. ProvisioningHoursEnd)
    or DayOfWeek in (6d, 0d)               // Flag weekend creation (Sat=6d, Sun=0d)
| project
    TimeGenerated, OperationName,
    InitiatedBy, InitiatedByApp,
    TargetUser, TargetUPN,
    HourOfDay, DayOfWeek
| sort by TimeGenerated desc
// Expected output: Accounts created outside business hours or on weekends
// ACTION: Verify with HR/IT provisioning team; unknown creators = investigate immediately
// FALSE POSITIVES: Automated provisioning systems, global teams in different time zones

T1098 . Account Manipulation: Privilege Escalation via Role Assignment

Hunt for unexpected role assignments, especially high-privilege roles like Global Administrator or Security Administrator:

// T1098 - Account Manipulation: Detect sensitive Entra ID role assignments
// PURPOSE: Hunt for unexpected privilege escalation via high-impact role grants
// WHY: Attackers assign themselves admin roles to maintain persistent elevated access
// MITRE ATT&CK: T1098 - Account Manipulation (Persistence / Privilege Escalation)
let SensitiveRoles = dynamic([
    "Global Administrator", "Security Administrator",
    "Exchange Administrator", "SharePoint Administrator",
    "Privileged Role Administrator", "Application Administrator",
    "Cloud Application Administrator", "User Administrator"
]);  // High-privilege roles that grant broad access - any unexpected assignment is critical
AuditLogs
| where TimeGenerated > ago(7d)                       // Last 7 days of audit activity
| where OperationName == "Add member to role"          // Role assignment operation
| extend
    TargetUser = tostring(TargetResources[0].userPrincipalName),   // Who received the role
    RoleAssigned = tostring(TargetResources[0].modifiedProperties[1].newValue), // Which role
    InitiatedBy = coalesce(
        tostring(InitiatedBy.user.userPrincipalName),  // Human who assigned the role
        tostring(InitiatedBy.app.displayName))         // Or app/automation that did it
| where RoleAssigned has_any (SensitiveRoles)          // Only high-privilege role assignments
| project
    TimeGenerated, InitiatedBy, TargetUser,
    RoleAssigned, OperationName,
    CorrelationId                                      // Use to correlate with other audit events
| sort by TimeGenerated desc
// Expected output: Recent sensitive role assignments with who-assigned-what-to-whom
// ACTION: Cross-reference with PIM activations and change management tickets
// Unexpected assignments = investigate immediately for privilege escalation attack
⚠️ Important: Legitimate Privileged Identity Management (PIM) activations will also appear in these results. Cross-reference with PIM activation logs and your organization’s change management records to filter out expected activity.

Step 5 · Build Hunting Queries for Lateral Movement (T1021 & T1550)

T1021 . Remote Services: Anomalous RDP/SSH Connections

Hunt for lateral movement through remote desktop or SSH by detecting connections between unusual host pairs:

// T1021 - Remote Services: Detect NEW RDP connections between hosts
// PURPOSE: Find lateral movement via RDP to host pairs never seen in the baseline
// WHY: Attackers use RDP to move between compromised systems - new pairs = anomalous
// MITRE ATT&CK: T1021 - Remote Services (Lateral Movement)
let Baseline = 14d;       // Baseline period: what’s "normal" for your environment
let HuntWindow = 1d;       // Hunt window: look for anomalies in the last 24 hours
//
// Step 1: Build baseline of known RDP source→target pairs from the past 14 days
let KnownPairs = SecurityEvent
    | where TimeGenerated between (ago(Baseline) .. ago(HuntWindow))
    | where EventID == 4624            // Windows logon success event
    | where LogonType == 10            // LogonType 10 = RemoteInteractive (RDP)
    | summarize by SourceIP = IpAddress, TargetHost = Computer;
//
// Step 2: Find recent RDP connections that are NOT in the baseline
SecurityEvent
| where TimeGenerated > ago(HuntWindow)  // Only the last 24 hours
| where EventID == 4624                  // Successful logon
| where LogonType == 10                  // RDP connections only
| extend SourceIP = IpAddress, TargetHost = Computer
| join kind=leftanti KnownPairs on SourceIP, TargetHost  // Remove known pairs
| summarize
    FirstSeen = min(TimeGenerated),      // When this new RDP pair first appeared
    LastSeen = max(TimeGenerated),       // Most recent connection
    ConnectionCount = count(),           // How many times this new pair connected
    Accounts = make_set(TargetUserName, 5)  // Which accounts were used
    by SourceIP, TargetHost
| sort by ConnectionCount desc
// Expected output: Newly observed RDP connections not seen in the prior 14 days
// ACTION: High ConnectionCount from unexpected sources = likely lateral movement
// FALSE POSITIVES: New admin workstations, IT help desk remote sessions

T1550 . Use Alternate Authentication Material: Pass-the-Token/Cookie

Hunt for token replay attacks by detecting sign-ins where the same token or session is used from different IP addresses:

// T1550 - Alternate Auth Material: Detect token replay from multiple IPs
// PURPOSE: Hunt for stolen tokens/cookies being reused from different network locations
// WHY: If the same session (CorrelationId) appears from multiple IPs, the token was stolen
// MITRE ATT&CK: T1550 - Use Alternate Authentication Material (Lateral Movement)
SigninLogs
| where TimeGenerated > ago(1d)            // Last 24 hours
| where ResultType == 0                    // Only successful authentications
| where TokenIssuerType == "AzureAD"       // Entra ID-issued tokens (not federated/on-prem)
| summarize
    IPCount = dcount(IPAddress),            // Unique IPs using the same session
    IPs = make_set(IPAddress, 10),          // List the IPs for investigation
    Countries = make_set(LocationDetails.countryOrRegion, 10),  // Geographic spread
    Apps = make_set(AppDisplayName, 5),     // Which apps the token accessed
    FirstSeen = min(TimeGenerated),         // Session start
    LastSeen = max(TimeGenerated)           // Most recent use
    by UserPrincipalName, CorrelationId     // Group by user + session correlation ID
| where IPCount > 1                        // Same session from >1 IP = token replay
| extend TimeDelta = LastSeen. FirstSeen   // Time span of the session
| where TimeDelta < 30m                    // Within 30 min = not a natural IP change
| sort by IPCount desc
// Expected output: Sessions where the same token was used from different IPs
// ACTION: High confidence token theft - revoke sessions, force re-authentication
// FALSE POSITIVES: VPN/proxy users may show multiple IPs; check named locations
💡 Pro Tip: VPN and proxy users may legitimately show multiple IPs. Enrich your results by cross-referencing with your organization’s known VPN egress IP ranges and named locations in Conditional Access.

Step 6 · Use Bookmarks to Preserve Hunting Evidence

When a hunting query returns interesting results, bookmarks let you save specific rows as evidence. Bookmarks are persisted in Sentinel and can be promoted to incidents.

Creating Bookmarks

  1. In the Hunting blade, run any of your hunting queries
  2. In the results pane, select one or more rows
  3. Click Add bookmark in the toolbar
  4. In the bookmark panel:
    • Bookmark Name: Use a descriptive name (e.g., "T1078. User jdoe@contoso.com signed in from Russia")
    • Tags: Add tags like T1078, InitialAccess, Hunt-2026-Q1
    • Notes: Document your hypothesis and findings
    • Map Entities: Account, IP, Host as appropriate
  5. Click Save

Managing Bookmarks

  1. Navigate to HuntingBookmarks tab
  2. Filter by tags, query name, or date range
  3. Select one or more bookmarks and click Incident actionsCreate new incident to escalate findings
  4. Alternatively, attach bookmarks to an existing incident for correlation
💡 Pro Tip: Develop a tag taxonomy for bookmarks that includes the hunt cycle (e.g., Hunt-2026-Q1-W3), the MITRE technique ID, and a severity rating. This makes it trivial to generate hunt reports and track patterns over time.

Step 7 · Configure Livestream Queries for Real-Time Monitoring

Livestream provides a near-real-time feed of hunting query results directly within the Sentinel portal. It is ideal for active hunting sessions where you want to observe an adversary’s actions as they happen.

Setting Up a Livestream Session

  1. In the Hunting blade, click Livestream in the top toolbar
  2. Click + New livestream
  3. Paste a KQL query. for example, a real-time version of the T1098 hunt:
// Livestream: Monitor sensitive Entra ID role assignments in real time
// PURPOSE: Get instant visibility into privilege escalation as it happens
// WHY: Role assignment attacks are fast - by the time a scheduled rule fires, damage is done
// MITRE ATT&CK: T1098 - Account Manipulation
// NOTE: Livestream queries run continuously and show results as they appear
AuditLogs
| where OperationName == "Add member to role"       // Only role assignment operations
| extend
    TargetUser = tostring(TargetResources[0].userPrincipalName),   // Who received the role
    RoleAssigned = tostring(TargetResources[0].modifiedProperties[1].newValue), // Which role
    InitiatedBy = coalesce(
        tostring(InitiatedBy.user.userPrincipalName),  // Human initiator
        tostring(InitiatedBy.app.displayName))         // Or app/automation initiator
| project TimeGenerated, InitiatedBy, TargetUser, RoleAssigned
// Expected output: Real-time feed of role assignments as they occur
// ACTION: Bookmark any unexpected assignments immediately for investigation
  1. Set a descriptive Name (e.g., "Livestream-T1098-RoleAssignment")
  2. Optionally configure an Alert action . Sentinel can raise an alert when the livestream query returns results
  3. Click Play to begin streaming
  4. Results appear in real time. Click any row to bookmark it directly from the livestream view.
⚠️ Important: Livestream queries consume workspace query capacity. Limit concurrent livestream sessions to 2·3 during active hunts and stop them when the hunting session concludes. Livestream sessions automatically stop after 12 hours.

Step 8 · Promote Hunting Results to Analytics Rules

When a hunting query consistently identifies true positive activity, promote it to a scheduled analytics rule so Sentinel detects the behavior automatically going forward.

Promotion Process

  1. In the Hunting blade, select the hunting query you want to promote
  2. Click · (ellipsis) → Create analytics rule
  3. Sentinel pre-fills the rule wizard with:
    • The KQL query
    • MITRE ATT&CK technique mappings
    • Entity mappings from the hunting query
  4. Configure the rule:
    • Name: Change from "Hunt-" prefix to a detection name (e.g., "Detect-T1078-NewCountrySignin")
    • Severity: Set based on your risk assessment (Medium, High)
    • Run frequency: Every 1 hour for high-priority hunts; every 4·24 hours otherwise
    • Lookup period: Match the hunting query’s lookback window
    • Alert threshold: Trigger when results > 0
  5. Under Automated response, optionally attach a playbook for automatic enrichment or notification
  6. Click Create
// Promoted analytics rule: Detect sign-ins from new (never-before-seen) countries
// Originally a hunting query (T1078), now automated as a scheduled detection
// Schedule: Run every 1h, lookback 1d - caught during hunting, now runs automatically
// MITRE ATT&CK: T1078 - Valid Accounts (Initial Access)
let LookbackPeriod = 14d;       // Baseline: 14 days of historical country data per user
let HuntingWindow = 1h;          // Detection window: check last hour for new countries
//
// Build per-user country baseline from the past 14 days
let KnownCountries = SigninLogs
    | where TimeGenerated between (ago(LookbackPeriod) .. ago(HuntingWindow))
    | where ResultType == 0                    // Only successful sign-ins for baseline
    | summarize by UserPrincipalName, Country = tostring(LocationDetails.countryOrRegion);
//
// Find sign-ins from countries NOT in each user’s baseline
SigninLogs
| where TimeGenerated > ago(HuntingWindow)     // Last hour only (matches run frequency)
| where ResultType == 0                        // Successful sign-ins = actual access granted
| extend Country = tostring(LocationDetails.countryOrRegion)
| join kind=leftanti KnownCountries on UserPrincipalName, Country  // Exclude known countries
| summarize
    SigninCount = count(),                     // Sign-ins from the new country
    IPs = make_set(IPAddress, 5)               // Source IPs for investigation
    by UserPrincipalName, Country
| where SigninCount >= 1                       // Any new-country sign-in triggers the alert
// Entity mapping: Account → UserPrincipalName, IP → IPs
💡 Pro Tip: After promoting a hunting query, keep the original hunting query active alongside the analytics rule. The hunting query serves as your research query with wider lookback windows, while the analytics rule handles real-time alerting with tighter windows.

Step 9 · Build a Hunting Coverage Workbook

Create a custom workbook that visualizes your MITRE ATT&CK hunting coverage, tracks hunt cycles, and shows coverage trends over time.

Create the Workbook

  1. Navigate to Microsoft SentinelWorkbooks+ Add workbook
  2. Click EditAdvanced Editor (code icon </>)
  3. Paste the following JSON template and click Apply:
{
  "version": "Notebook/1.0",
  "items": [
    {
      "type": 1,
      "content": {
        "json": "# ?? MITRE ATT&CK Hunting Coverage Dashboard\n\nThis workbook tracks threat hunting activity mapped to MITRE ATT&CK.\n\n---"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "HuntingBookmark\n| where TimeGenerated > ago(90d)\n| extend Technique = tostring(Tags)\n| summarize BookmarkCount = count() by Technique\n| sort by BookmarkCount desc",
        "size": 1,
        "title": "Bookmarks by MITRE Technique",
        "queryType": 0,
        "visualization": "barchart"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "HuntingBookmark\n| where TimeGenerated > ago(90d)\n| summarize Bookmarks = count() by bin(TimeGenerated, 7d)\n| render timechart",
        "size": 1,
        "title": "Hunting Activity Over Time (Weekly)",
        "queryType": 0,
        "visualization": "timechart"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "SecurityAlert\n| where TimeGenerated > ago(30d)\n| extend Techniques = extract_all(@\"(T\\d{4})\", tostring(ExtendedProperties))\n| mv-expand Technique = Techniques\n| summarize AlertCount = count() by tostring(Technique)\n| sort by AlertCount desc\n| take 20",
        "size": 1,
        "title": "Top 20 ATT&CK Techniques Triggering Alerts",
        "queryType": 0,
        "visualization": "barchart"
      }
    },
    {
      "type": 3,
      "content": {
        "version": "KqlItem/1.0",
        "query": "let AllTechniques = datatable(Tactic:string, TechniqueId:string) [\n  'InitialAccess', 'T1078',\n  'InitialAccess', 'T1566',\n  'Persistence', 'T1136',\n  'Persistence', 'T1098',\n  'LateralMovement', 'T1021',\n  'LateralMovement', 'T1550'\n];\nlet Covered = SecurityAlert\n| where TimeGenerated > ago(30d)\n| extend Techniques = extract_all(@\"(T\\d{4})\", tostring(ExtendedProperties))\n| mv-expand Technique = Techniques\n| distinct tostring(Technique);\nAllTechniques\n| extend IsCovered = iff(TechniqueId in (Covered), 'Covered', 'Gap')\n| summarize count() by Tactic, IsCovered",
        "size": 1,
        "title": "Coverage Gaps by Tactic",
        "queryType": 0,
        "visualization": "barchart"
      }
    }
  ],
  "styleSettings": {},
  "$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
}
  1. Save the workbook with the title "MITRE ATT&CK Hunting Coverage"
  2. Pin the workbook to your Sentinel dashboard for ongoing visibility
💡 Pro Tip: Extend the workbook by adding a parameter for date range selection and a tile strip showing total hunts completed, total bookmarks, and coverage percentage. Use HuntingBookmark table combined with a static ATT&CK technique list to calculate the percentage.

Step 10 · Establish a Recurring Threat Hunting Program

A one-time hunt has limited value. To achieve the government agency’s goal of reducing dwell time from 21 days to under 48 hours, you need a structured, recurring program.

Program Structure

Cadence Activity Owner
Weekly Execute all active hunting queries, review bookmark queue, update workbook Threat Hunter
Bi-weekly Develop 2·3 new hunting queries based on threat intelligence reports Threat Hunter + CTI Analyst
Monthly Review coverage workbook, promote successful hunts to analytics rules, retire low-value queries SOC Lead
Quarterly Compare ATT&CK coverage to baseline, publish hunt report to leadership, set priorities for next quarter CISO / SOC Manager

Hunt Hypothesis Template

Every hunt should start with a documented hypothesis. Use this format:

Hunt ID:         HUNT-2026-Q1-007
Hypothesis:      APT actors are using stolen OAuth tokens to access
                 mailboxes from infrastructure outside our known IP ranges.
MITRE Technique: T1550.001. Application Access Token
Data Sources:    SigninLogs, OfficeActivity, CloudAppEvents
KQL Query:       Hunt-T1550-OAuthTokenReuse
Timeframe:       Last 14 days
Expected Result: Identify sessions using tokens from IPs not in
                 our named location list.
Outcome:         [ ] True Positive → Incident
                 [ ] Benign True Positive → Allowlist
                 [ ] No Findings → Close hunt
                 [ ] Needs Tuning → Refine query
Analyst:         J. Smith
Date:            2026-03-05

Measuring Program Success

  • Coverage Growth: Track ATT&CK coverage percentage quarterly (target: 30% → 70% within 12 months)
  • Mean Time to Detect (MTTD): Measure reduction in dwell time for hunt-discovered incidents
  • Hunt-to-Detection Ratio: Percentage of hunting queries that become analytics rules
  • Bookmarks per Hunt Cycle: Volume of evidence captured per hunting sprint
  • True Positive Rate: Percentage of escalated bookmarks that result in confirmed incidents
💡 Pro Tip: Integrate your hunting program with the Sentinel Hunts feature (preview), which provides a built-in framework for managing hypotheses, assigning hunts to analysts, and tracking outcomes. all within the Sentinel portal.

Summary

What You Accomplished

  • Explored Sentinel's native MITRE ATT&CK integration and matrix view
  • Audited current analytics rule coverage against the ATT&CK framework
  • Built hunting queries for Initial Access (T1078, T1566)
  • Built hunting queries for Persistence (T1136, T1098)
  • Built hunting queries for Lateral Movement (T1021, T1550)
  • Used bookmarks to capture and annotate hunting evidence
  • Configured livestream queries for real-time threat monitoring
  • Promoted a hunting query to a scheduled analytics rule
  • Created a MITRE ATT&CK coverage workbook with JSON template
  • Defined a recurring threat hunting program with cadence and metrics

💰 Estimated Cost

Hunting queries run against data already ingested in your Log Analytics workspace. There is no additional cost for running hunting queries, bookmarks, or livestream beyond your existing workspace ingestion charges. Workbooks are free. Analytics rules are included in the Sentinel pricing tier.

🧹 Cleanup

  • Delete or disable any test hunting queries you do not wish to keep
  • Remove test bookmarks from the Hunting → Bookmarks tab
  • Stop any active livestream sessions
  • If you promoted a test analytics rule, disable or delete it under Analytics → Active rules
  • Delete the test workbook if not needed (or keep it for ongoing use)

🚀 Next Steps

  • Expand hunting queries to cover Execution (T1059), Privilege Escalation (T1548), and Defense Evasion (T1070) tactics
  • Integrate threat intelligence feeds to drive hypothesis-based hunting
  • Automate hunt execution with the Sentinel Hunting REST API
  • Explore Sentinel Notebooks for advanced data science-driven hunting
  • Build custom MITRE ATT&CK heatmaps using the ATT&CK Navigator

📚 Documentation & Resources

📚 Documentation Resources

ResourceDescription
Threat hunting in Microsoft SentinelOverview of proactive threat hunting capabilities
Hunting bookmarks in Microsoft SentinelSave and annotate hunting query results for investigation
Livestream in Microsoft SentinelMonitor hunting queries in real time with livestream sessions
Jupyter Notebooks in Microsoft SentinelAdvanced hunting and investigation with notebooks
MITRE ATT&CK FrameworkKnowledge base of adversary tactics, techniques, and procedures
MITRE ATT&CK coverage for Microsoft SentinelMap your detections to the MITRE ATT&CK framework
Hunting with the REST APIAutomate hunting queries programmatically
← Previous Lab All Labs →