Skip to content

Kusto Query Language (KQL)

This guide provides Mindflow users with a framework for using Kusto Query Language (KQL) within API endpoints where they can run KQL queries.

For instance, the MsGraph Security API endpoint /security/microsoft.graph.security.runHuntingQuery allow users to trigger searches across the tenant to detect potential malicious behavior and/or IOCs.

The following guide focuses on Efficiency (reducing cost/latency) and Hardening (minimizing false positives).

1. Core Operators and Performance Hardening

Section titled “1. Core Operators and Performance Hardening”

To prevent query timeouts and ensure accurate results, follow the “Filter Left” principle: keep high-cardinality filters at the top.

OperatorUsageHardening Tip
whereFilters the dataset.Use has or has_any instead of contains. has looks for full tokens and is significantly faster.
summarizeAggregates data.Use dcount() to find unique occurrences (e.g., unique IPs) rather than listing them all.
arg_max()Finds the latest record.Use summarize arg_max(TimeGenerated, *) by Entity to remove duplicate noise from logs.
parse_json()Extracts nested data.Always extend into a specific column before filtering to avoid repeated overhead.
searchScans across tables.Avoid in production. It is extremely resource-heavy. Explicitly name your tables instead.

2. Email Hunting (EmailEvents ans EmailAttachmentInfo)

Section titled “2. Email Hunting (EmailEvents ans EmailAttachmentInfo)”

This query identifies a single Sender IP delivering the same subject line to multiple recipients, a common sign of a coordinated campaign.

EmailEvents
| where TimeGenerated > ago(24h)
| where DeliveryAction !in ("Blocked", "Dropped")
| summarize RecipientCount = dcount(RecipientEmailAddress), SubjectList = make_set(Subject)
by SenderIPv4, SenderFromAddress
| where RecipientCount > 5
| sort by RecipientCount desc

Where you are already in possession of IOCs, you can also use queries based on hardcoded parameters, like senderFromAddress, SenderIPv4 or Subject, and use the OR operator to match either IOCs.

EmailEvents
| where Timestamp > ago(7d)
| where SenderFromAddress =~ "sender_email_address"
or Subject has "malicious_email_subject"
| summarize Recipients = make_set(RecipientEmailAddress) by NetworkMessageId, Subject, SenderFromAddress
| project NetworkMessageId, Subject, SenderFromAddress, Recipients

This query will correlate email metadata with attachment info to find high-risk file types arriving from external sources.

EmailEvents
| where TimeGenerated > ago(12h)
| where SenderFromDomain !has "yourdomain.com"
| join kind=inner (EmailAttachmentInfo) on NetworkMessageId
| where FileType in ("exe", "vbs", "ps1", "js", "htm", "html")
| project TimeGenerated, SenderFromAddress, RecipientEmailAddress, FileName, FileType, SHA256

A. Lateral Movement (Identity Correlation)

Section titled “A. Lateral Movement (Identity Correlation)”

Detects when a user signs in and immediately performs sensitive actions, such as adding a credential to an application.

let timeframe = 1h;
SigninLogs
| where TimeGenerated > ago(timeframe)
| where ResultType == 0
| join kind=inner (
AuditLogs
| where OperationName has_any ("Add service principal", "Add member to role")
) on $left.UserPrincipalName == $right.InitiatedBy.user.userPrincipalName
| project TimeGenerated, UserPrincipalName, OperationName, TargetResources[0].displayName

Hardened query to find processes running from suspicious temporary directories—a common tactic for malware.

DeviceProcessEvents
| where TimeGenerated > ago(1d)
| where FolderPath has_any ("\\Users\\Public\\", "\\AppData\\Local\\Temp\\", "\\Windows\\Temp\\")
| where FileName !in ("expected_updater.exe", "known_tool.exe") // Whitelist known-good binaries
| summarize FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by DeviceName, FileName, FolderPath, ProcessCommandLine

As best practice always try to follow the following when calling queries:

  • Limit Output: Always end your query with | take 100 or | limit 100 to prevent the API response from being truncated or timing out.
  • Timestamp Alignment: Use bin(TimeGenerated, 1h) to group events; this makes trend analysis and deduplication much easier for downstream logic apps.
  • Variable Injection: Use the let statement to define thresholds at the top of the script. This makes it easier to tune the query “hardness” without digging through the logic.
  • Finally, when you look to trigger queries, mind that you may have to update your supporting Azure Entra Application with additional permissions. For instance, to trigger a KQL on EmailEvents, your service principal should have the Email.Read.All or equivalent MsGraph permissions to see metadata, as some fields may be masked by default privacy settings.