KQL Threat Hunting Queries

IOC Sweep Patterns

IP IOC sweep across tables
let iocs = dynamic(["1.2.3.4", "5.6.7.8"]);
union
  (SigninLogs | where IPAddress in (iocs) | project TimeGenerated, Source="SigninLogs", Entity=UserPrincipalName, IOC=IPAddress),
  (CommonSecurityLog | where SourceIP in (iocs) or DestinationIP in (iocs) | project TimeGenerated, Source="CEF", Entity=DeviceProduct, IOC=SourceIP),
  (DeviceNetworkEvents | where RemoteIP in (iocs) | project TimeGenerated, Source="Endpoint", Entity=DeviceName, IOC=RemoteIP)
| sort by TimeGenerated desc
Domain IOC sweep
let domains = dynamic(["evil.com", "malware.net"]);
union
  (DnsEvents | where Name has_any (domains) | project TimeGenerated, Source="DNS", Entity=ClientIP, IOC=Name),
  (EmailUrlInfo | where Url has_any (domains) | project TimeGenerated, Source="Email", Entity=NetworkMessageId, IOC=Url),
  (DeviceNetworkEvents | where RemoteUrl has_any (domains) | project TimeGenerated, Source="Endpoint", Entity=DeviceName, IOC=RemoteUrl)
| sort by TimeGenerated desc

Anomaly Detection

Unusual process execution
DeviceProcessEvents
| where TimeGenerated > ago(24h)
| where FileName in ("powershell.exe", "cmd.exe", "wscript.exe", "cscript.exe", "mshta.exe", "certutil.exe")
| where InitiatingProcessFileName != "explorer.exe"
| summarize count() by DeviceName, FileName, InitiatingProcessFileName, ProcessCommandLine
| sort by count_ desc
First-time logon from new country
let baseline = SigninLogs
| where TimeGenerated between (ago(30d) .. ago(1d))
| distinct UserPrincipalName, LocationDetails_dynamic.countryOrRegion;
SigninLogs
| where TimeGenerated > ago(24h)
| where ResultType == "0"
| extend Country = tostring(LocationDetails_dynamic.countryOrRegion)
| join kind=leftanti (baseline | project UserPrincipalName, Country=Column2) on UserPrincipalName, Country
| summarize count() by UserPrincipalName, Country
Lateral movement indicators
SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4624
| where LogonType == 3
| where Account !endswith "$"
| summarize TargetMachines = dcount(Computer), Machines = make_set(Computer) by Account
| where TargetMachines > 5
| sort by TargetMachines desc

MITRE ATT&CK Quick Queries

Technique ID Detection Focus

Initial Access: Phishing

T1566

EmailEvents with ThreatTypes containing "Phish"

Execution: PowerShell

T1059.001

DeviceProcessEvents with powershell.exe and encoded commands

Persistence: Scheduled Task

T1053.005

SecurityEvent 4698 (task created)

Credential Access: Brute Force

T1110

SigninLogs with >10 failures per user per hour

Lateral Movement: Pass-the-Hash

T1550.002

SecurityEvent 4624 LogonType 3 with NTLM

Exfiltration: Large Upload

T1048

DeviceNetworkEvents with unusual outbound volume

Timeline Reconstruction

Build incident timeline from multiple sources
let target_user = "user@domain.com";
let incident_start = datetime(2026-04-10T08:00:00Z);
let incident_end = datetime(2026-04-10T20:00:00Z);
union
  (SigninLogs | where UserPrincipalName == target_user | where TimeGenerated between (incident_start .. incident_end) | project TimeGenerated, Source="Signin", Detail=strcat(ResultDescription, " from ", IPAddress)),
  (SecurityAlert | where Entities contains target_user | where TimeGenerated between (incident_start .. incident_end) | project TimeGenerated, Source="Alert", Detail=AlertName),
  (OfficeActivity | where UserId == target_user | where TimeGenerated between (incident_start .. incident_end) | project TimeGenerated, Source="Office", Detail=strcat(Operation, " ", OfficeObjectId))
| sort by TimeGenerated asc