Lab 03 Intermediate

Configure the Hunting Programme Starter Kit

60-90 minutes Modules: M11, M6, M10

Objective

Set up the operational infrastructure for a threat hunting programme. 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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 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

Query 2: Rare Process Execution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 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

Query 3: MFA Fatigue

1
2
3
4
5
6
7
8
// 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

Query 4: Cloud Account Creation Outside HR

1
2
3
4
5
6
7
8
9
// 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

Query 5: Privileged Role Assignment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 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

Query 6: Personal Cloud Storage Connections

1
2
3
4
5
6
7
8
9
// 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

Query 7: Encoded PowerShell

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 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

Query 8: Beacon Detection

1
2
3
4
5
6
7
8
// 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

Step 2: Create the DepartingEmployees watchlist

Navigate to: Sentinel → Watchlists → Create.

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,Low

Upload the CSV. Verify:

1
2
_GetWatchlist('DepartingEmployees')
| project SearchKey, DepartureDate, Department, RiskLevel

Step 3: Create the HuntLog watchlist

Navigate to: Sentinel → Watchlists → Create.

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 established

Upload 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:

FieldValue
SearchKeyHUNT-2026-04-001
HypothesisFirst-time country sign-in
TriggerMITRE gap (T1078.004)
StatusCompleted
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:

You now have a functioning hunting programme. 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.