Advanced Threat Hunting in Microsoft Sentinel

Most SOC teams spend their days responding to alerts. Alerts that fire, get triaged, get closed, get reopened. The cycle is exhausting — and it misses the point. The adversaries worth worrying about are the ones your alerts never catch.

Threat hunting inverts the model. Instead of waiting for the SIEM to raise its hand, a hunter starts with a hypothesis — a belief about how an attacker might be operating inside your environment — and goes looking for evidence. This article is about doing that in Microsoft Sentinel, with real queries you can run today.

1. Why Threat Hunting Matters

Detection engineering covers known TTPs. Threat hunting covers the unknown-knowns: techniques you haven’t written rules for yet, attackers who’ve already bypassed your perimeter, and lateral movement that looks like noise.

The numbers bear this out. Industry research consistently shows mean dwell time — how long adversaries sit undetected — measured in weeks. Alerts don’t catch them. Hunters do.

Three categories of hunting value for cloud SOC practitioners:

  • Hypothesis-driven hunting: You suspect credential harvesting is occurring; you look for it proactively.
  • Anomaly-based hunting: Baselines diverge — logins at 3am from a new country — you investigate before an alert would fire.
  • Intel-driven hunting: A threat feed publishes new IOCs; you hunt your environment against them immediately.

Hunting is not about replacing detections. It’s about discovering the gaps in your detection coverage — then closing them.

2. Sentinel’s Hunting Capabilities

Microsoft Sentinel ships with a dedicated hunting experience that most teams underuse. Here’s what matters:

Hunting Queries Gallery

Sentinel’s built-in gallery contains 200+ community-contributed KQL queries mapped to MITRE ATT&CK techniques. Use these as starting points, not endpoints. Every environment is different — tune aggressively.

Bookmarks

When a hunting query surfaces interesting results, bookmark the relevant rows. Bookmarks persist across sessions and can be promoted to incidents. This creates a traceable record of your investigation chain — critical for post-hunt reporting.

Livestream

Run a query, pin it as a Livestream, and Sentinel will re-execute it every 30 seconds. Useful when you’re actively hunting and want near-real-time feedback without spinning up a full alert rule.

Notebooks

For complex hunts — especially ML-assisted ones — Sentinel integrates with Azure ML notebooks. Bring your own Python, run statistical baselines, visualise anomaly clusters. The Jupyter environment connects directly to your Log Analytics workspace.

Watchlists

Watchlists let you bring external context into your KQL. Examples: high-value asset lists, known-admin IP ranges, terminated employee accounts. Joining watchlists to security events is one of the most powerful and underused capabilities in Sentinel.

3. Practical Hunting Queries

The following KQL queries are production-grade starting points. Each targets a specific TTP category. Adjust table names, time ranges, and thresholds for your environment.

Hunt 1: Impossible Travel / Credential Abuse

Detects the same account authenticating from geographically distant locations within a short window — a classic indicator of credential compromise or token theft.

// Impossible Travel Detection
let timeWindow = 2h;
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "0"  // Successful sign-ins only
| project TimeGenerated, UserPrincipalName,
          IPAddress, Location,
          lat = toreal(LocationDetails.geoCoordinates.latitude),
          lon = toreal(LocationDetails.geoCoordinates.longitude)
| join kind=inner (
    SigninLogs
    | where TimeGenerated > ago(24h)
    | where ResultType == "0"
    | project TimeGenerated2=TimeGenerated,
              UserPrincipalName,
              IPAddress2=IPAddress, Location2=Location,
              lat2=toreal(LocationDetails.geoCoordinates.latitude),
              lon2=toreal(LocationDetails.geoCoordinates.longitude)
) on UserPrincipalName
| where abs(TimeGenerated - TimeGenerated2) < timeWindow
| where IPAddress != IPAddress2
// Haversine approximation for distance
| extend DistKm = 111.2 * sqrt(
    pow(lat - lat2, 2) +
    pow((lon - lon2) * cos(radians((lat + lat2) / 2)), 2))
| where DistKm > 500
| project UserPrincipalName, TimeGenerated, Location,
          TimeGenerated2, Location2, DistKm
| order by DistKm desc

Hunt 2: Dormant Account Reactivation

Accounts that haven’t authenticated in 90+ days suddenly becoming active is a high-fidelity hunting signal — often indicates compromised credentials or insider threat.

// Dormant Account Reactivation Hunt
let dormantThreshold = 90d;
let recentWindow = 7d;
let dormantAccounts = SigninLogs
    | where TimeGenerated between (
          ago(dormantThreshold + 30d) .. ago(dormantThreshold))
    | where ResultType == "0"
    | summarize LastSeen = max(TimeGenerated)
              by UserPrincipalName
    | where LastSeen < ago(dormantThreshold);
SigninLogs
| where TimeGenerated > ago(recentWindow)
| where ResultType == "0"
| join kind=inner dormantAccounts on UserPrincipalName
| project UserPrincipalName, TimeGenerated,
          IPAddress, Location, LastSeen,
          DormantDays = datetime_diff('day',
              TimeGenerated, LastSeen)
| order by DormantDays desc

Hunt 3: Privilege Escalation via Role Assignment

Detecting new Azure RBAC Owner or Privileged Role Administrator assignments — especially outside business hours or from unfamiliar principals.

// Privilege Escalation: High-Priv Role Assignment
AuditLogs
| where TimeGenerated > ago(30d)
| where OperationName has "Add member to role"
| extend Role = tostring(
    TargetResources[0].modifiedProperties[1]
    .newValue)
| extend Actor = tostring(
    InitiatedBy.user.userPrincipalName)
| extend Target = tostring(
    TargetResources[0].userPrincipalName)
| where Role has_any (
    "Owner",
    "Global Administrator",
    "Privileged Role Administrator",
    "User Access Administrator")
| project TimeGenerated, Actor, Target,
          Role, CorrelationId
| order by TimeGenerated desc

Hunt 4: Exfil Candidate — Anomalous SharePoint Downloads

Identifies users downloading significantly more data than their personal 30-day baseline — a data exfiltration precursor signal.

// SharePoint Exfil: Baseline vs Spike
let baselineWindow = 30d;
let huntWindow = 1d;
let userBaseline = OfficeActivity
    | where TimeGenerated between (
          ago(baselineWindow) .. ago(huntWindow))
    | where Operation =~ "FileDownloaded"
    | summarize BaselineAvgDownloads =
          count() / 30.0
    by UserId;
OfficeActivity
| where TimeGenerated > ago(huntWindow)
| where Operation =~ "FileDownloaded"
| summarize TodayCount = count() by UserId
| join kind=inner userBaseline on UserId
| extend Multiplier = TodayCount / max_of(
      BaselineAvgDownloads, 1.0)
| where Multiplier > 5
| project UserId, TodayCount,
          BaselineAvgDownloads, Multiplier
| order by Multiplier desc

4. Real Example: Enterprise Threat Hunt

Scenario: Supply Chain Compromise via Partner Tenant

A financial services firm noticed unusual external collaboration activity in their M365 audit logs. The initial alert was low-severity — a guest account accessing SharePoint. A routine triage would have closed it. A hunter didn’t.

Phase 1 — Hypothesis Formation

The hunter’s hypothesis: if this guest account is compromised, the attacker will have used it to access high-value document libraries and potentially invite additional guests to extend their foothold.

Phase 2 — Initial Pivots

// Phase 2: Guest Account Activity Pivot
let suspectUPN = "external.user@partnerdomain.com";
union
    (SigninLogs | where UserPrincipalName =~ suspectUPN),
    (OfficeActivity | where UserId =~ suspectUPN),
    (AuditLogs
     | where InitiatedBy.user.userPrincipalName
            =~ suspectUPN)
| project TimeGenerated, Type,
          OperationName, IPAddress,
          Location, Details=tostring(pack_all())
| order by TimeGenerated asc

Result: 3 distinct IP addresses across 2 countries. The guest account had authenticated from both the partner’s known corporate IP range and a residential IP in a different geography on the same day.

Phase 3 — Lateral Discovery

// Phase 3: Guest-Initiated Invitations
AuditLogs
| where TimeGenerated > ago(14d)
| where InitiatedBy.user.userPrincipalName
        =~ "external.user@partnerdomain.com"
| where OperationName has_any (
    "Invite external user",
    "Add member to role",
    "Update application")
| project TimeGenerated, OperationName,
          TargetResources, CorrelationId

Result: The guest had invited two additional external addresses within 48 hours of their first anomalous login. Classic pivot-and-persist pattern.

Phase 4 — Containment

The hunt findings were escalated as a P1 incident. The guest account was disabled, the new external invitees were reviewed (one had already accepted), and partner tenant contacts were alerted. The entire hunt took 4 hours from hypothesis to containment.

Post-Hunt Outcome: 3 New Detection Rules

  • Guest account impossible travel rule (threshold: 500km in under 2 hours)
  • Guest-initiated external invitation within 72 hours of first login
  • Multi-IP authentication for B2B guest accounts with cross-session geolocation mismatch

The hunt found what no existing alert would have caught. That’s the entire point. Every successful hunt should close at least one detection gap.

5. Building a Repeatable Hunting Practice

Ad-hoc hunting is better than nothing. Systematic hunting compounds over time. Here’s the minimal viable structure:

  • Document every hunt in a shared notebook — hypothesis, queries, pivots, findings
  • Maintain a hunt backlog: new threat intel, customer environment changes, MITRE ATT&CK coverage gaps
  • Run dedicated hunt sprints — at minimum, 2 focused hunting sessions per month
  • Gate each hunt on a written hypothesis. No hypothesis, no hunt.
  • Every hunt should output either a new detection rule or a watchlist update

The teams that do this well treat hunting as a forcing function for detection engineering. The hunt uncovers the blind spot; the detection closes it; the coverage improves.


Published by SunExplains — Cloud SOC Practitioner Series | Surya, CISSP · SC-100 · AZ-500 · PCNSE

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Index