Objective
Set up the operational infrastructure for a threat hunting program. By the end of this lab, you have: 8 custom hunting queries in the Sentinel Hunting blade, a DepartingEmployees watchlist, a hunt log watchlist, and your first completed hunt with documented findings.
Required: Microsoft Sentinel workspace with populated data (SigninLogs, AuditLogs, CloudAppEvents minimum). Security Contributor role.
Step 1: Import 8 hunting queries
Navigate to: Sentinel → Hunting → Queries → New query.
Create each of the following custom hunting queries. For each: paste the KQL, assign the MITRE technique, set the required data source, and add a description.
Query 1: First-Time Country Sign-In
// Hunt: T1078.004 Valid Accounts — sign-in from a country the user has never used
let HistoricalCountries = SigninLogs
| where TimeGenerated between(ago(90d) .. ago(1d))
| where ResultType == "0"
| extend Country = tostring(LocationDetails.countryOrRegion)
| summarize Countries = make_set(Country) by UserPrincipalName;
SigninLogs
| where TimeGenerated > ago(1d)
| where ResultType == "0"
| extend Country = tostring(LocationDetails.countryOrRegion)
| join kind=inner HistoricalCountries on UserPrincipalName
| where Country !in (Countries)
| project TimeGenerated, UserPrincipalName, IPAddress, Country,
AppDisplayName, PreviousCountries = Countries- MITRE: T1078.004 (Valid Accounts: Cloud Accounts)
- Data source: SigninLogs
- Description: Identifies users signing in from countries they have never used in the last 90 days. New countries may indicate: compromised credentials used by an attacker, legitimate travel (verify with user), or VPN routing change.
Query 2: Rare Process Execution
// Hunt: T1059 Command and Scripting Interpreter — rare processes across endpoints
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| summarize
ExecutionCount = count(),
UniqueDevices = dcount(DeviceName),
Devices = make_set(DeviceName, 5)
by FileName, FolderPath
| where ExecutionCount < 3 and UniqueDevices == 1
| where FileName !in ("setup.exe", "update.exe", "installer.exe")
| order by ExecutionCount asc- MITRE: T1059.001 (PowerShell)
- Data source: DeviceProcessEvents
- Description: Finds processes that have executed only once or twice across the entire environment. Attacker tools are inherently rare.
Query 3: MFA Fatigue
// Hunt: T1621 MFA Request Generation
SigninLogs
| where TimeGenerated > ago(14d)
| where ResultType in ("50074", "500121")
| summarize DenialCount = count(),
DenialWindow = datetime_diff('minute', max(TimeGenerated), min(TimeGenerated))
by UserPrincipalName, IPAddress, bin(TimeGenerated, 1h)
| where DenialCount > 5- MITRE: T1621 (Multi-Factor Authentication Request Generation)
- Data source: SigninLogs
- Description: Detects repeated MFA denials — the pattern of MFA fatigue attacks where the attacker spams push notifications.
Query 4: Cloud Account Creation Outside HR
// Hunt: T1136.003 Create Cloud Account
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName == "Add user"
| where Result == "success"
| extend CreatedBy = tostring(InitiatedBy.user.userPrincipalName)
| extend NewUser = tostring(TargetResources[0].userPrincipalName)
| where CreatedBy !has "provisioning" and CreatedBy !has "serviceaccount"
| project TimeGenerated, CreatedBy, NewUser- MITRE: T1136.003 (Create Account: Cloud Account)
- Data source: AuditLogs
Query 5: Privileged Role Assignment
// Hunt: T1098.003 Additional Cloud Roles
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName has "Add member to role"
| where Result == "success"
| extend RoleName = tostring(TargetResources[0].modifiedProperties[1].newValue)
| extend AssignedTo = tostring(TargetResources[0].userPrincipalName)
| extend AssignedBy = tostring(InitiatedBy.user.userPrincipalName)
| where RoleName has_any ("Global Admin", "Security Admin", "Exchange Admin")
| project TimeGenerated, AssignedTo, RoleName, AssignedBy- MITRE: T1098.003 (Account Manipulation: Additional Cloud Roles)
- Data source: AuditLogs
Query 6: Personal Cloud Storage Connections
// Hunt: T1567 Exfiltration Over Web Service
DeviceNetworkEvents
| where TimeGenerated > ago(14d)
| where RemoteUrl has_any ("dropbox.com", "drive.google.com", "icloud.com",
"mega.nz", "wetransfer.com", "onedrive.live.com")
| summarize ConnectionCount = count(), UniqueDevices = dcount(DeviceName)
by AccountName, RemoteUrl
| where ConnectionCount > 10
| order by ConnectionCount desc- MITRE: T1567 (Exfiltration Over Web Service)
- Data source: DeviceNetworkEvents
Query 7: Encoded PowerShell
// Hunt: T1059.001 PowerShell with encoding
DeviceProcessEvents
| where TimeGenerated > ago(14d)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("-enc", "-EncodedCommand",
"FromBase64String", "Invoke-Expression", "IEX")
| extend CommandLength = strlen(ProcessCommandLine)
| where CommandLength > 500
| project TimeGenerated, DeviceName, AccountName,
Command = substring(ProcessCommandLine, 0, 200), CommandLength- MITRE: T1059.001 (Command and Scripting Interpreter: PowerShell)
- Data source: DeviceProcessEvents
Query 8: Beacon Detection
// Hunt: C2 beaconing — regular-interval external connections
DeviceNetworkEvents
| where TimeGenerated > ago(7d)
| where ActionType == "ConnectionSuccess"
| where not(ipv4_is_private(RemoteIP))
| summarize Connections = count() by DeviceName, RemoteIP
| where Connections > 50
| order by Connections desc- MITRE: T1071 (Application Layer Protocol)
- Data source: DeviceNetworkEvents
Step 2: Create the DepartingEmployees watchlist
Navigate to: Sentinel → Watchlists → Create.
- Name: DepartingEmployees
- Alias: DepartingEmployees
- SearchKey: SearchKey
Create a CSV file with these columns and 3 test entries:
SearchKey,DepartureDate,Department,RiskLevel
testuser1@yourdomain.com,2026-05-01,Engineering,High
testuser2@yourdomain.com,2026-05-15,Finance,Medium
testuser3@yourdomain.com,2026-04-30,Marketing,LowUpload the CSV. Verify:
_GetWatchlist('DepartingEmployees')
| project SearchKey, DepartureDate, Department, RiskLevelStep 3: Create the HuntLog watchlist
Navigate to: Sentinel → Watchlists → Create.
- Name: HuntLog
- Alias: HuntLog
- SearchKey: SearchKey
CSV columns:
SearchKey,Hypothesis,Trigger,Status,Analyst,StartDate,TimeSpentHours,Outcome,RulesCreated,Notes
HUNT-2026-04-001,First-time country sign-in may indicate credential compromise,MITRE gap,Completed,YourName,2026-04-10,1.5,NoFindings,0,Lab exercise - baseline establishedUpload with your first hunt record (Step 4 below).
Step 4: Execute your first hunt
Hypothesis: "An attacker may have used compromised credentials to sign in from a country no Northgate Engineering user has previously used, within the last 24 hours."
Step 4.1: Run Query 1 (First-Time Country Sign-In) from the Hunting blade.
Step 4.2: Review results. For each result: is the country suspicious? Is it a known travel destination? Cross-reference with the user's department and role.
Step 4.3: If any result is suspicious: create a bookmark. Map entities: Account, IP. Tag: HUNT-2026-04-001, T1078.004, suspicious or benign.
Step 4.4: Document the hunt in the HuntLog watchlist:
| Field | Value |
|---|---|
| SearchKey | HUNT-2026-04-001 |
| Hypothesis | First-time country sign-in |
| Trigger | MITRE gap (T1078.004) |
| Status | Completed |
| Analyst | [Your name] |
| StartDate | [Today] |
| TimeSpentHours | [Actual time] |
| Outcome | [NoFindings / Suspicious / ThreatConfirmed] |
| RulesCreated | [0 or 1] |
| Notes | [Brief findings summary] |
Step 4.5: If the hunt confirmed a threat: promote the bookmark to an incident. Create an analytics rule from the hunting query. If no findings: document the negative result. Both outcomes are valid.
Verification
You have completed this lab when:
- [ ] 8 custom hunting queries visible in Sentinel → Hunting → Queries
- [ ] Each query has MITRE technique and data source assigned
- [ ] DepartingEmployees watchlist created with test entries
- [ ] HuntLog watchlist created with at least 1 hunt record
- [ ] First hunt executed, results reviewed, and documented
- [ ] At least 1 bookmark created (even for a benign finding)
You now have a functioning hunting program. Set a calendar reminder: execute one hunt per fortnight using the queries you just imported. Rotate through the 8 queries over 4 months. Log every hunt in the HuntLog. After 3 months: you have institutional knowledge about your environment's threat baseline that no vendor product can provide.