Threat hunting isn’t about waiting for an alert. It’s about proactively testing hypotheses against your telemetry looking for weak signals that automated detections may miss.
Microsoft Sentinel is built for this style of work, but the real unlock is KQL (Kusto Query Language). You don’t need to memorize everything. You need a small set of commands, a repeatable workflow, and a library of patterns you can adapt.
What you’ll learn
- How threat hunting differs from detection and investigation
- Where to hunt in Sentinel (tables, time windows, and fields)
- The essential KQL “hunter’s kit”
- Reusable hunting query patterns (copy/paste)
- How to interpret results without fooling yourself
Threat hunting vs detection vs investigation (in 30 seconds)
- Detection: analytics rules generate alerts from known patterns.
- Investigation: you follow an alert and reconstruct the timeline.
- Threat hunting: you start with a hypothesis (e.g., “quiet lateral movement”) and search data for traces.
Hunting is proactive. It’s how mature SOCs find real issues before they become incidents.
Prerequisites (so hunting is useful)
Before writing queries, make sure you have:
- The right connectors enabled (Microsoft 365, Azure, Defender, Windows, etc.)
- Enough retention to see patterns (7 days is often not enough)
- A naming/tagging convention for workspaces, tables, and VIP accounts
- A clear objective: “reduce time to detect,” “detect exfiltration,” “find persistence,” etc.
Where to hunt: Sentinel tables and the datastore
In Sentinel, your data lives in Log Analytics as tables. Each connector feeds one or more tables.
Quick ways to discover what you have
Start broad, then narrow:
1) Global search (when you only have a clue)
kql
search "keyword"
| where TimeGenerated > ago(24h)
| take 200
2) Always time-bound your hunts
kql
| where TimeGenerated > ago(24h)
3) Project only what you need
kql
| project TimeGenerated, Computer, Account, IPAddress, OperationName
Tip: begin with 1 hour → expand to 24 hours → then 7 days. Don’t start with “all time.”
The KQL hunter’s kit (commands you’ll use constantly)
where (filter)
kql
| where Account contains "admin"
| where IPAddress startswith "10."
project (select columns)
kql
| project TimeGenerated, Account, IPAddress, ActionType
extend (create a field)
kql
| extend Hour = datetime_part("hour", TimeGenerated)
summarize (aggregate)
kql
| summarize count() by Account
| summarize dcount(IPAddress) by Account
order by and take
kql
| order by TimeGenerated desc
| take 50
join (correlate across tables)
kql
| join kind=inner (...) on DeviceId
parse / extract (pull structured values out of text)
Use these when fields are semi-structured (common in custom logs).
mv-expand (expand arrays)
Use this when a field contains a list (IPs, URLs, recipients, etc.).
Hunting query patterns (copy/paste and adapt)
These are patterns, not “one-size-fits-all.” Tables depend on your connectors (Defender, Entra ID, M365, etc.).
Pattern A: abnormal authentication (multiple countries)
kql
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0
| summarize Countries = make_set(LocationDetails.countryOrRegion) by UserPrincipalName
| where array_length(Countries) > 1
When it’s useful: spotting impossible travel, unusual access locations, or compromised credentials.
Pattern B: MFA/sign-in failures with eventual success (brute force + success)
kql
SigninLogs
| where TimeGenerated > ago(24h)
| summarize Failures = countif(ResultType != 0), Success = countif(ResultType == 0) by UserPrincipalName
| where Failures > 10 and Success > 0
| order by Failures desc
Why it matters: repeated failures followed by success can indicate password spraying, MFA fatigue, or credential stuffing.
Pattern C: broad search when you only have one indicator
kql
search "rundll32"
| where TimeGenerated > ago(7d)
| take 200
Use this early in an investigation when you’re hunting for a known LOLBin, file name, or suspicious string.
Pattern D: suspicious PowerShell execution (common attacker behaviors)
DeviceProcessEvents
| where TimeGenerated > ago(24h)
| where FileName in~ ("powershell.exe", "pwsh.exe")
| where ProcessCommandLine has_any ("-enc", "IEX", "DownloadString", "FromBase64String")
| project TimeGenerated, DeviceName, AccountName, ProcessCommandLine
| order by TimeGenerated desc
Pattern E: correlate a successful sign-in with device logons (join)
kql
let suspiciousUsers = SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == 0
| summarize by UserPrincipalName;
DeviceLogonEvents
| where TimeGenerated > ago(24h)
| join kind=inner suspiciousUsers on $left.AccountUpn == $right.UserPrincipalName
| project TimeGenerated, DeviceName, AccountUpn, LogonType
Why it matters: correlation turns “a list” into “a story.”
How to analyze results (without fooling yourself)
Avoid classic false positives
- Service accounts and automation accounts
- VPN/proxy hops (same user, different IP)
- Internal scanning tools and scheduled jobs
Move from “list” to “story”
A good hunting result should let you answer:
- Who did it? (account)
- What happened? (action)
- Where did it occur? (device, IP, country)
- When did it happen? (timeline)
- What next? (potential impact / follow-on activity)
If you can’t tell the story, you’re not done hunting yet—you’re just collecting rows.
Next steps (turn hunts into repeatable capability)
- Pick one hypothesis (e.g., encoded PowerShell execution).
- Run the query over 24 hours, then 7 days.
- Add one correlation (join) to enrich context.
- Convert the best hunts into reusable assets: saved queries, workbooks, or analytics rules (where appropriate).
Recommended training path (Eccentrix)
If you want your team to hunt and investigate effectively (not just run canned queries), training matters.
- Microsoft SC-200: Microsoft Security Operations Analyst (Sentinel + Defender + investigation)
- Microsoft SC-900: Security, Compliance, and Identity Fundamentals (for baseline knowledge)
- SC-5001: Configure SIEM security operations using Microsoft Sentinel (specialized Sentinel configuration)
FAQ
Do I need to be a developer to use KQL?
No. KQL is designed for log analytics. With a small set of commands (where, project, summarize), you can produce meaningful hunts quickly.
What’s the difference between a hunting query and an analytics rule?
A hunting query is exploratory and hypothesis-driven. An analytics rule is automated detection that generates alerts.
Which tables should I start with?
Start with the tables tied to your core connectors (e.g., SigninLogs, AuditLogs, and Defender tables such as DeviceProcessEvents, DeviceNetworkEvents, etc.).
How do I reduce false positives?
Use time bounds, create allowlists for known automation, add context with join, and tune thresholds with summarize.
Top comments (0)