<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: hunting-with-kql-queries
description: >-
  Write and optimize Kusto Query Language (KQL) queries for threat hunting
  in Microsoft Sentinel, Defender for Endpoint, and Azure Data Explorer.
  Covers operators, aggregations, joins, time-series analysis, and hunt hypotheses.
domain: cybersecurity
subdomain: blue-team
tags:
  - kql
  - threat-hunting
  - microsoft-sentinel
  - defender-for-endpoint
  - azure
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1070"]
---

# Hunting with KQL Queries

## Overview

Kusto Query Language (KQL) is the query language for Microsoft Sentinel,
Defender for Endpoint (MDE), and Azure Data Explorer. It enables threat
hunters to search, correlate, and visualize security telemetry at scale
with a pipe-based functional syntax.

## Prerequisites

| Requirement | Purpose |
|---|---|
| Microsoft Sentinel workspace | Log Analytics query target |
| Defender for Endpoint license | `DeviceProcessEvents` tables |
| Azure CLI or portal access | Query execution |
| KQL syntax knowledge | Pipe-based query construction |

## Key Concepts

### Essential Operators

```kql
// where — filter rows
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName =~ "powershell.exe"

// project — select columns
| project Timestamp, DeviceName, AccountName, ProcessCommandLine

// extend — computed columns
| extend CommandLength = strlen(ProcessCommandLine)

// summarize — aggregate
| summarize Count = count() by DeviceName, bin(Timestamp, 1h)

// join — correlate tables
DeviceProcessEvents
| where FileName == "cmd.exe"
| join kind=inner (
    DeviceNetworkEvents
    | where RemotePort == 443
) on DeviceId, Timestamp
```

### Threat Hunting Patterns

#### Rare Process Execution (Stack Counting)

```kql
DeviceProcessEvents
| where Timestamp > ago(7d)
| summarize ExecutionCount = count(), Devices = dcount(DeviceName)
    by FileName, FolderPath
| where ExecutionCount < 5 and Devices == 1
| sort by ExecutionCount asc
```

#### Encoded PowerShell Commands

```kql
DeviceProcessEvents
| where Timestamp > ago(24h)
| where FileName == "powershell.exe"
| where ProcessCommandLine has_any ("-enc", "-EncodedCommand", "FromBase64String")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine
| sort by Timestamp desc
```

#### Suspicious Parent-Child Process Relationships

```kql
DeviceProcessEvents
| where Timestamp > ago(24h)
| where InitiatingProcessFileName in~ ("winword.exe", "excel.exe", "outlook.exe")
| where FileName in~ ("cmd.exe", "powershell.exe", "wscript.exe", "mshta.exe")
| project Timestamp, DeviceName, InitiatingProcessFileName, FileName, ProcessCommandLine
```

#### Beaconing Detection (Time-Series)

```kql
DeviceNetworkEvents
| where Timestamp > ago(24h)
| where RemoteIPType == "Public"
| summarize ConnectionTimes = make_list(Timestamp) by DeviceName, RemoteIP
| extend Intervals = array_sort_asc(ConnectionTimes)
| mv-apply Intervals on (
    extend NextTime = next(Intervals)
    | extend Delta = datetime_diff('second', NextTime, Intervals)
    | summarize AvgDelta = avg(Delta), StdDev = stdev(Delta), Count = count()
)
| where Count > 20 and StdDev < AvgDelta * 0.1
| project DeviceName, RemoteIP, AvgDelta, StdDev, Count
```

#### Failed Logon Brute Force

```kql
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType != "0"
| summarize FailedAttempts = count(), DistinctUsers = dcount(UserPrincipalName)
    by IPAddress, bin(TimeGenerated, 5m)
| where FailedAttempts > 20
| sort by FailedAttempts desc
```

### Performance Optimization

| Technique | Example |
|---|---|
| Time filter first | `where Timestamp > ago(24h)` as first operator |
| Use `has` over `contains` | `has` uses term index, `contains` does full scan |
| Avoid `*` in project | Select only needed columns |
| Use `in` over multiple `or` | `where X in ("a","b","c")` |
| Pre-filter before join | Filter both sides before joining |

## Workflow

1. **Hypothesis** — Define what adversary behavior to find (ATT&CK-based)
2. **Data source** — Identify KQL table (`DeviceProcessEvents`, `SigninLogs`, etc.)
3. **Baseline** — Query normal behavior to establish thresholds
4. **Hunt query** — Write detection query with appropriate operators
5. **Validate** — Test against known-good and known-bad scenarios
6. **Tune** — Adjust thresholds, add exclusions for legitimate activity
7. **Promote** — Convert successful hunts to analytics rules

## Detection

```yaml
title: Hunting With Kql Queries Detection
id: e5901928-23b0-4944-a45a-d5a910dfb420
status: experimental
description: Detects suspicious activity related to hunting with kql queries techniques in blue team context
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    CommandLine: "*hunting*with*"
  condition: selection
level: medium
tags:
  - attack.t1070
  - attack.defense_evasion
falsepositives:
  - Security team running authorized detection validation tools
```

## Verification

| Check | Method |
|---|---|
| Query syntax valid | Runs without error in Sentinel/MDE |
| Time scoping correct | `where Timestamp > ago(Xd)` present |
| Results actionable | Output includes device, user, and command context |
| Performance acceptable | Query completes within 30 seconds |
| False positive rate | < 10% of results are benign |

## References

- [KQL Quick Reference](https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/)
- [Microsoft Sentinel Hunting Queries](https://github.com/Azure/Azure-Sentinel/tree/master/Hunting%20Queries)
- [MDE Advanced Hunting Schema](https://learn.microsoft.com/en-us/defender-endpoint/advanced-hunting-schema-tables)
