Кибербезопасность и информационная безопасность

KQL для начинающих: первые запросы для расследования инцидентов

6 мин чтенияОпубликовано: 5 августа 2026 г.
Профессиональная визуальная иллюстрация по теме KQL для начинающих в области SIEM и обнаружения
Быстрый ответ

KQL — Kusto Query Language — это язык запросов для чтения и анализа данных в таких продуктах, как Azure Monitor и Microsoft Sentinel. Запрос обычно начинается с таблицы и продолжается по конвейеру команд: фильтрация по времени и событиям, выбор или создание полей, агрегирование по пользователю или ресурсу и отображение результатов, относящихся к расследованию. Ключ к обучению — начать с одного вопроса и построить запрос шаг за шагом.

Аналитику SOC не нужно запоминать сотни команд, чтобы начать работать с KQL. Ему нужно понять модель мышления: в какой таблице находится информация, какой временной диапазон, какие строки релевантны, какие поля нужны и как обобщить результаты, чтобы ответить на вопрос расследования.

KQL — это язык для чтения и анализа. Это не SQL, хотя есть похожие концепции. Данные текут слева направо через Pipe — символ | — и каждая строка получает результат предыдущей строки. Таким образом, можно построить небольшой поиск, проверить результат и добавить следующий шаг, не записывая все сразу.

Примеры в руководстве используют общие имена таблиц и полей, но Schema меняется между рабочими областями и источниками. Прежде чем копировать Query, откройте несколько записей, проверьте фактические поля и скорректируйте логику. Все данные в упражнении являются смоделированными.

Базовая структура запроса

Первая строка обычно указывает Table. Каждый Pipe добавляет Operator. Например, запрос, который отображает десять последних входов из таблицы SigninLogs:

SigninLogs
| where TimeGenerated > ago(24h)
| project TimeGenerated, UserPrincipalName, IPAddress, ResultType
| sort by TimeGenerated desc
| take 10

Прочитайте запрос как предложение: возьмите SigninLogs, сохраните только события за последние сутки, отобразите четыре поля, отсортируйте от нового к старому и возьмите десять строк. Порядок шагов важен как для понимания, так и для производительности. Ранняя фильтрация уменьшает количество данных, передаваемых на следующие шаги.

Шаг 1: Изучите таблицу

Перед расследованием запустите несколько строк, чтобы увидеть Schema и примеры. Команда take отлично подходит для обучения, но не гарантирует последних событий, если не сортировать. Можно использовать project для сокращения отображения и project-away для скрытия ненужных полей.

SigninLogs
| take 5

Обратите внимание на поля типов datetime, string, dynamic и int. Поле dynamic может содержать JSON или массив и иногда требует parse_json, mv-expand или доступа к внутреннему свойству. Не предполагайте, что ResultType одинаков для всех источников. В документации таблицы или в фактических примерах проверьте значение значений.

Шаг 2: Фильтрация с помощью where

where является основным инструментом расследования. Можно фильтровать по времени, пользователю, IP, результату или тексту. Лучше начинать с узкого временного диапазона и использовать сравнения, подходящие для типа поля.

SigninLogs
| where TimeGenerated between (datetime(2026-08-01 08:00:00) .. datetime(2026-08-01 12:00:00))
| where UserPrincipalName =~ "student@contoso.example"
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, ResultType

Оператор =~ сравнивает строки без учета регистра. Для частичного поиска можно использовать contains, has или startswith. Во многих случаях has более эффективен и точен для поиска полного термина. Избегайте очень широкой фильтрации с помощью contains, когда можно использовать встроенное поле.

Шаг 3: Выбор полей и создание контекста

project возвращает только выбранные поля. extend создает новое поле, не удаляя существующие. Это полезно для меток, расчетов и локальной нормализации.

SigninLogs
| where TimeGenerated > ago(24h)
| extend Outcome = iff(tostring(ResultType) == "0", "Success", "Failure")
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Outcome

При расследовании отдавайте предпочтение именам полей, которые проясняют значение. Можно использовать project-rename для изменения имени для отображения, но не скрывайте источник данных. В профессиональном Ticket должно быть указано, из какой Table и поля был получен каждый важный вывод.

Шаг 4: summarize — превращение событий в картину

summarize группирует события и вычисляет Aggregations. Можно считать, находить первое и последнее время, собирать значения, вычислять количество уникальных пользователей и строить Baseline. BY определяет группы.

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Attempts=count(),
            FirstSeen=min(TimeGenerated),
            LastSeen=max(TimeGenerated),
            Applications=make_set(AppDisplayName, 10)
  by UserPrincipalName, IPAddress
| sort by Attempts desc

make_set полезен для отображения различных значений, но большие списки затрудняют чтение. Ограничьте количество элементов. dcount дает приблизительный подсчет уникальных значений и подходит для широкого анализа; distinct возвращает уникальные комбинации, когда их нужно увидеть.

countif и dcountif

В расследованиях Authentication иногда хочется подсчитать успехи и неудачи в одной группе. countif считает только строки, которые соответствуют условию:

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            UniqueUsers=dcount(UserPrincipalName)
  by IPAddress
| where Failures >= 5
| sort by Failures desc

Результат — Lead. Он не доказывает, что успех произошел после неудач, и что общий IP может быть NAT, Proxy или VPN. Необходимо открыть исходные события и построить Timeline до Classification.

Временные окна с помощью bin

bin группирует datetime в фиксированные сегменты. Это позволяет видеть Burst активности вместо подведения итогов за весь день. Выберите Span в зависимости от поведения: Password Spray может растянуться на длительное время, чтобы избежать порога; Brute Force против одной учетной записи может быть быстрым.

SigninLogs
| where TimeGenerated > ago(24h)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            Users=dcount(UserPrincipalName)
  by IPAddress, bin(TimeGenerated, 30m)
| where Failures >= 10 and Successes >= 1
| sort by TimeGenerated desc

Столбец TimeGenerated в результате представляет начало Bin. Чтобы увидеть точный порядок, используйте результат для выбора IP и окна, а затем запустите второй Query, который отображает события по времени.

let — чтобы сделать запрос читаемым

let позволяет присвоить имя значению или временной таблице. Это полезно для определения Time range, Threshold или набора данных, которые используются несколько раз.

let Lookback = 24h;
let FailureThreshold = 10;
SigninLogs
| where TimeGenerated > ago(Lookback)
| summarize Failures=countif(tostring(ResultType) != "0"),
            Successes=countif(tostring(ResultType) == "0"),
            Users=dcount(UserPrincipalName),
            UserList=make_set(UserPrincipalName, 20)
  by IPAddress, bin(TimeGenerated, 30m)
| where Failures >= FailureThreshold and Successes >= 1
| project TimeGenerated, IPAddress, Failures, Successes, Users, UserList
| order by Failures desc

Четкие имена и комментарии уменьшают количество ошибок. В KQL можно добавить Comment с помощью //. Query, который планируется превратить в Analytics rule, должен описывать цель обнаружения, предположения, источники, версию и Owner.

Практическое упражнение: неудачи, а затем успешный вход

В лаборатории запустите итоговый Query на смоделированных данных. Выберите строку, в которой есть Failures и Successes. Затем выполните Drill-down:

let TargetIP = "203.0.113.25";
let WindowStart = datetime(2026-08-01 09:00:00);
SigninLogs
| where TimeGenerated between (WindowStart .. WindowStart + 30m)
| where IPAddress == TargetIP
| extend Outcome = iff(tostring(ResultType) == "0", "Success", "Failure")
| project TimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, Outcome, ResultDescription
| order by TimeGenerated asc

Ответьте на вопросы: Повлияли ли неудачи на одного или нескольких пользователей? Принадлежит ли успех тому же пользователю? Известен ли IP как VPN? Была ли активирована MFA? Есть ли дополнительная активность после успеха? Только после сбора контекста можно определить, является ли это Password Spray, ошибкой пользователя, старой службой или санкционированной активностью.

Оптимизация и безопасность работы

  • Фильтруйте время раньше и используйте максимально точную таблицу.
  • Фильтруйте по встроенным полям вместо текстового поиска по Raw data.
  • Отображайте только необходимые поля с помощью project.
  • Избегайте join на больших объемах, прежде чем проверять альтернативы, такие как lookup или summarize.
  • Ограничивайте make_set и make_list.
  • Проверяйте Query на небольшом диапазоне перед расширением.
  • Документируйте предположения, Thresholds и значение значений Result.
  • Не превращайте Query в Detection, прежде чем проверять True/False/Benign Positives.

Распространенные ошибки

  • Копировать Query, не проверив местную Schema.
  • Подводить итоги за весь день и делать вывод о последовательности событий.
  • Забывать временной диапазон и сканировать большой объем без необходимости.
  • Использовать разные имена полей для двух источников, как если бы они были одинаковыми.
  • Рассматривать Query result как доказательство атаки.
  • Создавать огромные списки с помощью make_set.
  • Писать один длинный Query вместо проверки каждого шага.

Заключение и призыв к действию

Откройте среду Log Analytics или лабораторию с смоделированными данными и постройте Query в три шага: отобразите пять строк, отфильтруйте одно событие и обобщите по пользователю или IP. Сохраните каждую версию и объясните, на что она отвечает. Затем перейдите к руководству по расследованию Incident в Microsoft Sentinel, чтобы увидеть, как Query интегрируется в реальный Case.

Вопросы и ответы

Похож ли KQL на SQL?

Есть общие концепции, такие как фильтрация, Projection и Aggregation, но синтаксис и модель Pipe отличаются. Стоит изучать KQL как самостоятельный язык.

В чем разница между where и search?

where фильтрует по выражению и определенным полям и предпочтителен в большинстве расследований. search может искать значения более широко, но иногда менее точен и эффективен.

Может ли Query изменять или удалять логи?

KQL в контексте Log Analytics и Sentinel используется для чтения и анализа. Операции управления и Ingestion выполняются с помощью других механизмов и соответствующих разрешений.

В чем разница между summarize и distinct?

distinct возвращает уникальные комбинации. summarize группирует и вычисляет значения, такие как count, min, max или make_set.

Как узнать, какую Table искать?

Начинайте с Data connector и документации Schema, используйте поиск Tables в Log Analytics и проверяйте примеры событий. Один и тот же Use Case может использовать разные таблицы в разных организациях.

Хотите проверить, подходит ли вам эта программа?

Оставьте контакты — консультант HPI перезвонит вам для короткого разговора, без обязательств.

Ваши данные хранятся безопасно.

Для обучения SOC и кибербезопасности в рамках программы Cybersecurity & AI

Хотите узнать подробности программы? Оставьте контакты — мы свяжемся.

Похожие статьи