Cyber Intelligence
Microsoft Sentinel · 50-55% of exam

L9. KQL Fundamentals: Querying Security Data

Video generating

Check back soon for the video lesson on KQL Fundamentals: Querying Security Data

Kusto Query Language (KQL) is the foundation of threat hunting and analytics in Microsoft Sentinel and Defender XDR. This lesson covers essential operators, time-based filtering, joins, and the query patterns SC-200 candidates encounter most on the exam.

Why KQL Matters for SC-200

KQL is used throughout the Microsoft security stack: Sentinel analytics rules, hunting queries, workbooks, Defender XDR advanced hunting, and Log Analytics queries. The SC-200 exam includes KQL-based questions where you must read, write, or correct queries.

Essential Operators

Every KQL query starts with a table name and pipes data through operators:

SecurityEvent
| where TimeGenerated > ago(24h)
| where EventID == 4625
| summarize FailedLogons = count() by Account
| sort by FailedLogons desc
| take 10

Key operators for the exam:

OperatorPurpose
whereFilter rows based on conditions
summarizeAggregate data (count, sum, avg, dcount)
projectSelect specific columns
extendAdd calculated columns
sort by / order bySort results
take / limitReturn first N rows
joinCombine data from two tables
unionAppend rows from multiple tables
renderVisualize results as charts
parseExtract values from string fields
letDefine variables or reusable subqueries

Time-Based Filtering

The ago() function is essential for security queries:

SigninLogs
| where TimeGenerated > ago(7d)
| where ResultType != 0

Common time literals: 1h (hour), 1d (day), 7d (week), 30d (month). Exam tip: Always filter by TimeGenerated early in the query. It is the most effective way to reduce the data scanned and improve query performance.

The summarize Operator

summarize is the most tested operator. It groups and aggregates data:
SecurityEvent
| where EventID == 4624
| summarize LogonCount = count(),
    UniqueAccounts = dcount(Account)
    by Computer
| where LogonCount > 100

Key aggregation functions: count(), dcount() (distinct count), sum(), avg(), min(), max(), make_set() (unique values as array), make_list() (all values as array).

Joins

Joins combine data from two tables based on a shared key:

SecurityEvent
| where EventID == 4625
| project TimeGenerated, Account, Computer
| join kind=inner (
    SigninLogs
    | where ResultType != 0
    | project TimeGenerated, UserPrincipalName, IPAddress
) on $left.Account == $right.UserPrincipalName

Join kinds for the exam:

  • inner: Only matching rows from both tables
  • leftouter: All rows from the left table, matching from the right
  • rightouter: All rows from the right table, matching from the left
  • fullouter: All rows from both tables
  • leftanti: Rows from the left table with no match in the right
Exam tip: leftanti is commonly used to find entities that are NOT in a watchlist or threat intelligence list.

The let Statement

let defines reusable variables or subqueries:
let suspiciousIPs = dynamic(["10.0.0.5", "192.168.1.100"]);
SigninLogs
| where IPAddress in (suspiciousIPs)
| project TimeGenerated, UserPrincipalName, IPAddress, Location

Practical Security Queries

Failed logon brute force detection:

SecurityEvent
| where TimeGenerated > ago(1h)
| where EventID == 4625
| summarize Attempts = count() by Account, Computer
| where Attempts > 10

New process execution on a specific host:

DeviceProcessEvents
| where TimeGenerated > ago(1d)
| where DeviceName == "workstation01"
| project TimeGenerated, FileName, ProcessCommandLine, AccountName
| sort by TimeGenerated desc
Exam Focus Points
  • Always filter by TimeGenerated early in the query for performance optimization.
  • The summarize operator with count(), dcount(), and make_set() is heavily tested on the exam.
  • leftanti join finds entities NOT present in a second table, useful for watchlist exclusions.
  • The let statement defines reusable variables and subqueries within a KQL query.
  • project selects columns, extend adds calculated columns, and parse extracts values from strings.
Knowledge Check

1. You need to find user accounts that appear in the SecurityEvent table but NOT in a watchlist table. Which join type should you use?

2. Which KQL aggregation function returns the number of unique values in a column?

3. What is the most effective way to improve KQL query performance when querying large Sentinel tables?