Microsoft Sentinel 中的 Kusto 查询语言

Kusto 查询语言是将在 Microsoft Sentinel 中用于处理和操作数据的语言。 如果无法分析馈送到工作区中的日志,并且无法获取隐藏在所有这些数据中的重要信息,那么这些日志便没有多大的价值。 Kusto 查询语言不仅具备获取该信息所需的功能和灵活性,还可通过简单方式帮助你快速入门。 如果你有编写数据库脚本或处理数据库方面的背景知识,则会对本文中的许多内容感到熟悉。 如果没有,请不要担心,因为该语言的直观性将很快使你能够开始编写自己的查询,并为组织带来价值。

本文介绍 Kusto 查询语言的基础知识,其中涵盖了一些最常用的函数和运算符,这些函数和运算符应该能够处理每天编写的 75% 到 80% 的查询。 如果需要更深入的信息或运行更高级的查询,可以利用新的 Microsoft Sentinel 高级 KQL 工作簿(请参阅此介绍性博客文章)。 另请参阅官方 Kusto 查询语言文档以及各种在线课程(如 Pluralsight 的在线课程)。

背景 - 为何使用 Kusto 查询语言?

Microsoft Sentinel 基于 Azure Monitor 服务构建而成,它使用 Azure Monitor 的 Log Analytics 工作区来存储其所有数据。 该数据包括以下任意一项:

  • 使用 Microsoft Sentinel 数据连接器从外部源引入到预定义表中的数据。
  • 使用自定义创建的数据连接器以及某些类型的现成连接器,从外部源引入到用户定义的自定义表中的数据。
  • 由 Microsoft Sentinel 本身创建的数据,这些数据来源于它创建和执行的分析(例如警报、事件和 UEBA 相关信息)。
  • 上传到 Microsoft Sentinel 以帮助进行检测和分析的数据,例如威胁情报源和监视列表。

Kusto 查询语言作为 Azure 数据资源管理器服务的一部分开发,因此已针对在云环境中搜索大数据存储进行了优化。 它受著名海底探险家 Jacques Cousteau(相应发音为“koo-STOH”)的启发,旨在帮助你深入到数据海洋,探索它们隐藏的宝藏。

Kusto 查询语言还用于 Azure Monitor(因此也用于 Microsoft Sentinel),包括一些其他 Azure Monitor 功能,让你可以检索、可视化、分析和解析 Log Analytics 数据存储中的数据。 无论是在现有规则和工作簿中,还是在构建自己的规则和工作簿时,每当可视化和分析数据以及搜寻威胁时,都可以使用基于 Kusto 查询语言的工具。

由于 Kusto 查询语言是在 Microsoft Sentinel 中所做的几乎所有工作的一部分,因此,清楚地了解其工作原理将有助于从 SIEM 中得到更多。

什么是查询?

Kusto 查询语言查询是一种只读请求,用于处理数据并返回结果 - 它不写入任何数据。 查询对组织为数据库层次结构的数据进行操作,类似于 SQL。

请求以简明语言表示,并使用旨在使语法易于读取、写入和自动化的数据流模型。 我们将详细了解这一点。

Kusto 查询语言查询由使用分号分隔的语句组成。 语句的类型有多种,但此处仅讨论两种广泛使用的类型:

  • Tabular 表达式语句是我们在讨论查询时通常所表达的含义 - 它们是查询的实际主体。 有关 tabular 表达式语句,需要了解的重要的一点是,它们接受表格输入(一个表或其他 tabular 表达式),并且生成表格输出。 至少其中一项是必需的。 本文的其余大部分内容将对此类语句进行探讨。

  • Let 语句使你能够在查询主体之外创建和定义变量和常量,以提高可读性和通用性。 这些语句是可选语句,并且取决于你的特定需求。 我们将在本文末尾讨论此类语句。

演示环境

可以在 Azure 门户的 Log Analytics 演示环境中练习 Kusto 查询语言语句(包括本文中的语句)。 使用此练习环境不收取任何费用,但需要 Azure 帐户才能访问该环境。

浏览演示环境。 与生产环境中的 Log Analytics 一样,可通过多种方式使用演示环境:

  • 选择要对其生成查询的表。 在默认的“表”选项卡中(显示在左上方的红色长方形中),从已按主题分组的表列表中选择一个表(显示在左下方)。 展开主题以查看各个表,还可以进一步展开每个表以查看其所有字段(列)。 双击某个表或字段名称会将该表或字段名称置于查询窗口中的光标处。 在表名称后面键入查询的其余部分,如下所示。

  • 查找要研究或修改的现有查询。 选择“查询”选项卡(显示在左上角的红色长方形中)以查看现成可用的查询列表。 或者,从右上角的按钮栏中选择“查询”。 可以浏览随 Microsoft Sentinel 提供的现成查询。 双击某个查询会将整个查询放置在查询窗口中的光标处。

    Shows the Log Analytics demo environment.

与在此演示环境中一样,可以在 Microsoft Sentinel“日志”页中查询和筛选数据。 可以选择一个表并向下钻取以查看列。 可以使用列选择器修改显示的默认列,并且可以设置查询的默认时间范围。 如果在查询中显式定义了时间范围,时间筛选器将不可用(灰显)。

查询结构

若要开始学习 Kusto 查询语言,了解整体查询结构是一种不错选择。 在查看 Kusto 查询时,将注意到的第一件事情是管道符号 (|) 的使用。 Kusto 查询的结构始于从数据源获取数据,然后通过“管道”传递数据,每个步骤都提供一定程度的处理,然后将数据传递到下一个步骤。 在管道的末尾,将获得最终结果。 实际上,这就是我们的管道:

Get Data | Filter | Summarize | Sort | Select

这种沿着管道传递数据的概念形成了一种非常直观的结构,因为在每个步骤中都可以轻松创建数据的记忆图。

为了演示这一点,让我们看看以下查询,该查询会查看 Microsoft Entra 登录日志。 在通读每一行时,可以看到指示数据所发生的情况的关键字。 我们已将管道中的相关阶段作为注释包含在每一行中。

注意

可为查询中的任一行添加注释,只需在该行的前面加上双斜杠 (//) 即可。

SigninLogs                              // Get data
| evaluate bag_unpack(LocationDetails)  // Ignore this line for now; we'll come back to it at the end.
| where RiskLevelDuringSignIn == 'none' // Filter
   and TimeGenerated >= ago(7d)         // Filter
| summarize Count = count() by city     // Summarize
| sort by Count desc                    // Sort
| take 5                                // Select

由于每个步骤的输出都充当下一个步骤的输入,因此步骤的顺序可以确定查询的结果并影响其性能。 根据你要从查询中获得的结果将步骤排序至关重要。

提示

  • 一条很好的经验法则是提前筛选数据,以便只通过管道传递相关的数据。 这将极大地提高性能,并确保不会意外地在汇总步骤中包含不相关的数据。
  • 本文将指出一些需要记住的其他最佳做法。 有关更完整的列表,请参阅查询最佳做法

希望你现在了解 Kusto 查询语言中查询的整体结构。 现在,让我们看看实际查询运算符本身,这些运算符用于创建查询。

数据类型

在了解查询运算符之前,让我们先快速了解一下数据类型。 与在大多数语言中一样,数据类型决定了可以针对值运行的计算和操作。 例如,如果值的类型为 string,则将无法对其进行算术计算。

在 Kusto 查询语言中,大多数数据类型都遵循标准约定,并具有你之前可能见过的名称。 下表显示了完整列表:

数据类型表

类型 其他名称 等效 .NET 类型
bool Boolean System.Boolean
datetime Date System.DateTime
dynamic System.Object
guid uuiduniqueid System.Guid
int System.Int32
long System.Int64
real Double System.Double
string System.String
timespan Time System.TimeSpan
decimal System.Data.SqlTypes.SqlDecimal

虽然大部分数据类型是标准类型,但你可能对“动态”、“时间范围”和“guid”等类型不很熟悉。

动态类型的结构与 JSON 非常相似,但两者存在一项重要差别:动态类型可以存储特定于 Kusto 查询语言的数据类型,例如嵌套的动态值或时间范围,而传统的 JSON 则不可以。 下面是动态类型的示例:

{
"countryOrRegion":"US",
"geoCoordinates": {
"longitude":-122.12094116210936,
"latitude":47.68050003051758
},
"state":"Washington",
"city":"Redmond"
}

时间范围是引用时间度量(例如小时、天或秒)的一种数据类型。 请不要将时间范围与日期/时间相混淆,后者计算为实际日期和时间,而不是时间度量。 下表显示了时间范围后缀的列表。

Timespan 后缀

函数 说明
D days
H 小时
M 分钟数
S seconds
Ms 毫秒
Microsecond 微秒
Tick 纳秒

Guid 是表示 128 位全局唯一标识符的数据类型,它遵循标准格式 [8]-[4]-[4]-[4]-[12],方括号中的每个数字代表字符数量,每个字符的范围可以是 0-9 或 a-f

注意

Kusto 查询语言具有表格和标量运算符。 在本文的其余部分中,除非另有说明,否则如果只看到“运算符”一词,则可以假定它的意思是表格运算符。

获取数据,并对数据进行限制、排序和筛选

Kusto 查询语言的核心词汇是一组用于筛选数据以及对数据进行排序和选择的运算符,并且是使你能够完成绝大多数任务的基础。 需要执行的其余任务将需要扩展你在该语言方面的知识以便满足更高级的需求。 让我们稍微扩展一下在上述示例中使用的一些命令,看看 takesortwhere

对于这些运算符,我们将在前面的 SigninLogs 示例中检查其用法,并了解有用的技巧或最佳做法。

获取数据

任何基本查询的第一行均指定要使用的表。 对于 Microsoft Sentinel,这可能是工作区中日志类型的名称,例如 SigninLogs、SecurityAlert 或 CommonSecurityLog 。 例如:

SigninLogs

请注意,在 Kusto 查询语言中,日志名称区分大小写,因此 SigninLogssigninLogs 的解释将不同。 在为自定义日志选择名称时请小心,以便它们易于识别,并且不会与其他日志太相似。

限制数据:take / limit

Take 运算符(以及相同的 limit 运算符)用于通过仅返回给定数量的行来限制结果。 它后跟一个整数,该整数指定要返回的行数。 通常,在确定排序顺序后,它在查询末尾使用,在这种情况下,它将根据排序顺序返回给定行数。

如果不想返回大型数据集,在查询中早先使用 take 可能对测试查询非常有用。 但是,如果将 take 操作置于任何 sort 操作之前,take 将返回随机选择的行,并且在每次运行查询时可能会返回一组不同的行。 下面是一个使用 take 的示例:

SigninLogs
      | take 5

Screenshot of results of take operator.

提示

在处理一个你不太有把握的全新查询时,将 take 语句放在开头,从而人为地限制数据集以加快处理和试验速度可能会很有用。 对完整查询感到满意后,可以删除最初的 take 步骤。

将数据排序:sort / order

Sort 运算符(以及相同的 order 运算符)用于按指定列对数据进行排序。 在以下示例中,我们已按 TimeGenerated 将结果排序,并使用 desc 参数将排序方向设置为降序(最大值放在最前面);要设置为升序,可以使用 asc。

注意

默认的排序方向是降序,因此从技术上讲,你只需指定是否要按升序排序。 但是,在任何情况下指定排序方向都会使查询更具可读性。

SigninLogs
| sort by TimeGenerated desc
| take 5

如前所述,我们将 sort 运算符置于 take 运算符之前。 首先需要排序,以确保获得正确的五条记录。

Screenshot of results of sort operator, with take limit.

顶部

Top 运算符允许将 sorttake 操作合并到单个运算符中:

SigninLogs
| top 5 by TimeGenerated desc

如果两条或多条记录在排序所依据的列中具有相同的值,可以添加更多排序要依据的列。 在逗号分隔的列表中添加额外的排序列,添加位置位于第一个排序列之后,但位于排序顺序关键字之前。 例如:

SigninLogs
| sort by TimeGenerated, Identity desc
| take 5

现在,如果多条记录之间的 TimeGenerated 相同,则会尝试按 Identity 列中的值进行排序。

注意

何时使用 sorttake,以及何时使用 top

  • 如果仅对一个字段进行排序,请使用 top,因为它提供的性能优于 sorttake 的组合。

  • 如果需要对多个字段进行排序(如上面的最后一个示例中所示),top 无法进行此排序,因此必须使用 sorttake

筛选数据:where

Where 运算符可以说是最重要的运算符,因为它是确保仅处理与方案相关的数据子集的关键。 应尽可能早地在查询中筛选数据,因为这样做可以减少后续步骤中需要处理的数据量,从而提高查询性能;它还可确保仅对所需数据执行计算。 请参阅此示例:

SigninLogs
| where TimeGenerated >= ago(7d)
| sort by TimeGenerated, Identity desc
| take 5

where 运算符指定变量、比较(标量)运算符和值。 我们使用 >= 表示 TimeGenerated 列中的值需要大于(即晚于)或等于七天前。

Kusto 查询语言包含两种类型的比较运算符:字符串和数值。 下表显示了数值运算符的完整列表:

数值运算符

运算符 描述
+ 加法
- 减法
* 乘法
/ 除法
% 取模
< 小于
> 大于
== 等于
!= 不等于
<= 小于或等于
>= 大于或等于
in 等于其中一个元素
!in 不等于任何元素

字符串运算符的列表要长得多,因为它包含区分大小写、子字符串位置、前缀、后缀等的排列。 == 运算符既是数值运算符,也是字符串运算符,这意味着它可以同时用于数字和文本。 例如,以下两个语句都是有效的 where 语句:

  • | where ResultType == 0
  • | where Category == 'SignInLogs'

提示

最佳做法:在大多数情况下,你可能希望按多个列筛选数据,或者以多种方式筛选同一列。 在这些情况下,应记住两种最佳做法。

可以使用 and 关键字,将多个 where 语句合并为单个步骤。 例如: 。

SigninLogs
| where Resource == ResourceGroup
    and TimeGenerated >= ago(7d)

当使用 and 关键字将多个筛选器联接到单个 where 语句中时,如上所示,通过将仅引用单个列的筛选器置于首位,可以获得更好的性能。 因此,编写上述查询的更好的方法是:

SigninLogs
| where TimeGenerated >= ago(7d)
    and Resource == ResourceGroup

在此示例中,第一个筛选器提及了单个列 (TimeGenerated),第二个筛选器引用了两个列(Resource 和 ResourceGroup) 。

汇总数据

Summarize 是 Kusto 查询语言中最重要的表格运算符之一,但如果你不熟悉一般的查询语言,它还是要了解的更复杂的运算符之一。 summarize 的工作是接收数据表,并输出由一个或多个列聚合的新表。

Summarize 语句的结构

summarize 语句的基本结构如下所示:

| summarize <aggregation> by <column>

例如,以下语句将返回 Perf 表中每个 CounterName 值的记录计数:

Perf
| summarize count() by CounterName

Screenshot of results of summarize operator with count aggregation.

由于 summarize 的输出是一个新表,因此 summarize 语句中未显式指定的任何列都将不会沿管道传递。 为了说明此概念,请考虑以下示例:

Perf
| project ObjectName, CounterValue, CounterName
| summarize count() by CounterName
| sort by ObjectName asc

在第二行,我们指定仅关注 ObjectName、CounterValue 和 CounterName 列 。 然后,我们进行了汇总,以便按 CounterName 获取记录计数,最后,我们尝试根据 ObjectName 列按升序顺序对数据进行排序 。 遗憾的是,此查询将失败,并且出现错误(指示 ObjectName 未知),因为在汇总时,我们仅在新表中包含了 Count 和 CounterName 列 。 若要避免此错误,只需将 ObjectName 添加到 summarize 步骤的末尾,如下所示:

Perf
| project ObjectName, CounterValue , CounterName
| summarize count() by CounterName, ObjectName
| sort by ObjectName asc

你脑海中读取 summarize 行的方法是:“按 CounterName 汇总记录计数,并按 ObjectName 进行分组” 。 可以继续将使用逗号分隔的列添加到 summarize 语句的末尾。

Screenshot of results of summarize operator with two arguments.

沿用前面的示例,如果我们想要同时聚合多个列,可以通过向 summarize 运算符添加逗号分隔的聚合来做到这一点。 在以下示例中,我们不仅获取了所有记录的计数,还获取了所有记录(与查询中的任何筛选器匹配)中 CounterValue 列的值的总和:

Perf
| project ObjectName, CounterValue , CounterName
| summarize count(), sum(CounterValue) by CounterName, ObjectName
| sort by ObjectName asc

Screenshot of results of summarize operator with multiple aggregations.

重命名聚合列

现在似乎是时候讨论这些聚合列的列名了。 在本部分开始时,我们提到 summarize 运算符接收数据表并生成一个新表,并且只有在 summarize 语句中指定的列才会继续沿管道传递。 因此,如果要运行上述示例,聚合的结果列将是 count_ 和 sum_CounterValue 。

Kusto 引擎会自动创建列名,无需我们明确指出,但通常情况下,你会发现,你希望新列的名称更加易记。 可以通过指定一个新名称,后跟 = 和聚合,来轻松地重命名 summarize 语句中的列,如下所示:

Perf
| project ObjectName, CounterValue , CounterName
| summarize Count = count(), CounterSum = sum(CounterValue) by CounterName, ObjectName
| sort by ObjectName asc

现在,汇总的列将命名为 Count 和 CounterSum。

Screenshot of friendly column names for aggregations.

此处只是介绍了有关 summarize 运算符的很少一部分知识,但你应该投入时间来学习它,因为在你对 Microsoft Sentinel 数据执行任何数据分析时,它都是一个关键的组件。

聚合引用

聚合函数有很多,但最常用的一些聚合函数为 sum()count()avg()。 下面是部分列表(请参阅完整列表):

聚合函数

函数 说明
arg_max() 当参数最大化时返回一个或多个表达式
arg_min() 当参数最小化时返回一个或多个表达式
avg() 返回整个组的平均值
buildschema() 返回允许动态输入的所有值的最小架构
count() 返回组的计数
countif() 返回具有组谓词的计数
dcount() 返回组元素的近似非重复计数
make_bag() 返回一个在组内包含动态值的属性包
make_list() 返回组中所有值的列表
make_set() 返回组中非重复值的集合
max() 返回组内的最大值
min() 返回组内的最小值
percentiles() 返回组的百分位近似值
stdev() 返回整个组的标准偏差
sum() 返回组中元素的总和
take_any() 返回组的随机非空值
variance() 返回整个组的方差

选择:添加和删除列

在开始更多地使用查询时,你可能会发现你所拥有的主题信息比你所需要的要多(也就是说,表中的列过多)。 或者,你可能需要比现有信息更多的信息(也就是说,你需要添加一个包含其他列分析结果的新列)。 让我们来看看一些用于列操作的关键运算符。

Project 和 project-away

Project 大致等效于多种语言的 select 语句 。 它允许选择要保留的列。 返回的列顺序将与 project 语句中列出的列顺序匹配,如以下示例所示:

Perf
| project ObjectName, CounterValue, CounterName

Screenshot of results of project operator.

可以想象,在处理非常宽的数据集时,你可能要保留大量的列,而按名称指定这些列需要键入大量内容。 对于这种情况,可以使用 project-away,它允许指定要删除的列(而不是要保留的列),如下所示:

Perf
| project-away MG, _ResourceId, Type

提示

在查询的开头和末尾这两个位置使用 project 会很有用。 早先在查询中使用 project 可以去除无需沿管道传递的大量数据,从而帮助提高性能。 在末尾再次使用它,可以去除在前面步骤中创建的并且在最终输出中不需要的任何列。

延长

Extend 用于创建新的计算列。 如果希望对现有列执行计算并查看每一行的输出,这非常有用。 让我们来看一个简单的示例,在此示例中,我们计算一个名为 Kbytes 的新列,我们可以通过将 MB 值(在现有 Quantity 列中)乘以 1,024 来进行计算 。

Usage
| where QuantityUnit == 'MBytes'
| extend KBytes = Quantity * 1024
| project ResourceUri, MBytes=Quantity, KBytes

project 语句的最后一行,我们将 Quantity 列重命名为 Mbytes,这样我们便可以轻松地判断与每个列相关的度量单位 。

Screenshot of results of extend operator.

值得注意的是,extend 也适用于已计算的列。 例如,我们可以再添加一个基于 Kbytes 计算得出的名为 Bytes 的列:

Usage
| where QuantityUnit == 'MBytes'
| extend KBytes = Quantity * 1024
| extend Bytes = KBytes * 1024
| project ResourceUri, MBytes=Quantity, KBytes, Bytes

Screenshot of results of two extend operators.

联接表

可以使用单个日志类型来执行 Microsoft Sentinel 中的大部分工作,但有时需要将数据关联在一起或对另一组数据执行查找操作。 与大多数查询语言一样,Kusto 查询语言提供了一些用于执行各种类型的联接的运算符。 在本部分中,我们将介绍最常用的运算符 unionjoin

联合

Union 仅获取两个或多个表,并返回所有行。 例如:

OfficeActivity
| union SecurityEvent

这将返回 OfficeActivity 和 SecurityEvent 表中的所有行 。 Union 提供了几个参数,这些参数可用于调整联合的行为。 其中两个最有用的参数是 withsource 和 kind :

OfficeActivity
| union withsource = SourceTable kind = inner SecurityEvent

使用 withsource 参数可以指定新列的名称,该列在给定行中的值将是该行所在的表的名称。 在上面的示例中,我们将列命名为 SourceTable,并且根据行的不同,值为 OfficeActivity 或 SecurityEvent 。

我们指定的另一个参数是 kind,它有两个选项:inner 或 outer 。 在上面的示例中,我们指定了 inner,这意味着在联合期间将仅保留这两个表中都存在的列。 或者,如果我们指定了 outer(这是默认值),则将返回这两个表中的所有列。

Join

Join 的工作原理与 union 类似,不同之处在于它是通过联接行来创建新表,而不是通过联接表来创建新表 。 与大多数数据库语言一样,可以执行多种类型的联接。 join 的一般语法为:

T1
| join kind = <join type>
(
               T2
) on $left.<T1Column> == $right.<T2Column>

join 运算符之后,指定要执行的联接类型,后跟一个左括号。 括号内是指定要联接的表的位置,以及要添加的该表上的任何其他查询语句。 在右括号后面,使用 on 关键字,后接左($left.<columnName> 关键字)和右 ($right.<columnName>) 列,用 == 运算符分隔。 下面是内部联接的示例:

OfficeActivity
| where TimeGenerated >= ago(1d)
    and LogonUserSid != ''
| join kind = inner (
    SecurityEvent
    | where TimeGenerated >= ago(1d)
        and SubjectUserSid != ''
) on $left.LogonUserSid == $right.SubjectUserSid

注意

如果这两个表与你正在对其执行联接的列同名,则无需使用 $left 和 $right;只需指定列名即可。 但是,使用 $left 和 $right 更为明确,并且通常被认为是一种不错的做法 。

下表列出了可用的联接类型,以供参考。

联接类型

联接类型 说明
inner 返回两个表中每个匹配行组合的单个组合。
innerunique 返回左表中的行,其中链接字段中的不同值与右表中的项相匹配。
这是默认的未指定联接类型。
leftsemi 返回左表中与右表中的项相匹配的所有记录。
仅返回左表中的列。
rightsemi 返回右表中与左表中的项相匹配的所有记录。
仅返回右表中的列。
leftanti/
leftantisemi
返回左表中不与右表中的项相匹配的所有记录。
仅返回左表中的列。
rightanti/
rightantisemi
返回右表中不与左表中的项相匹配的所有记录。
仅返回右表中的列。
leftouter 返回左表中的所有记录。 对于在右表中没有匹配项的记录,单元格值将为 null。
rightouter 返回右表中的所有记录。 对于在左表中没有匹配项的记录,单元格值将为 null。
fullouter 返回左表和右表中的所有记录,而不管是否匹配。
不匹配的值将为 null。

提示

最佳做法将最小的表放在左侧。 在某些情况下,根据要执行的联接类型和表的大小,遵循此规则可带来巨大的性能优势。

评估

你可能记得,在第一个示例中,我们在其中一行上看到了 evaluate 运算符。 evaluate 运算符不如我们之前提到的运算符那么常用。 不过,了解 evaluate 运算符的工作原理是非常值得的。 再说一次,下面是第一个查询,你将在第二行看到 evaluate

SigninLogs
| evaluate bag_unpack(LocationDetails)
| where RiskLevelDuringSignIn == 'none'
   and TimeGenerated >= ago(7d)
| summarize Count = count() by city
| sort by Count desc
| take 5

此运算符允许调用可用插件(基本上是内置函数)。 其中许多插件都专注于数据科学,例如 autoclusterdiffpatternssequence_detect,使你能够执行高级分析并发现统计异常和离群值 。

上述示例中使用的插件名为 bag_unpack,使用它可以十分轻松地提取动态数据区块并将其转换为列。 请记住,动态数据是一种与 JSON 非常相似的数据类型,如以下示例所示:

{
"countryOrRegion":"US",
"geoCoordinates": {
"longitude":-122.12094116210936,
"latitude":47.68050003051758
},
"state":"Washington",
"city":"Redmond"
}

在本例中,我们希望按城市 (city) 汇总数据,但 city 作为一个属性包含在 LocationDetails 列中。 若要在查询中使用 city 属性,必须先使用 bag_unpack 将其转换为列 。

回到最初的管道步骤,我们看到了:

Get Data | Filter | Summarize | Sort | Select

现在我们已经考虑了 evaluate 运算符,我们可以看到它代表管道中的一个新阶段,如下所示:

Get Data | Parse | Filter | Summarize | Sort | Select

还有许多其他运算符和函数示例,可用于将数据源解析为可读性和操作性更强的格式。 可以在完整的文档工作簿中了解它们以及 Kusto 查询语言的其余内容。

Let 语句

现在,我们已经介绍了许多主要的运算符和数据类型,让我们最后再来介绍一下 let 语句,该语句可使查询更易于读取、编辑和维护。

Let 使你能够创建和设置变量,或为表达式指定名称。 此表达式可以是单个值,但也可以是整个查询。 下面是一个简单示例:

let aWeekAgo = ago(7d);
SigninLogs
| where TimeGenerated >= aWeekAgo

在这里,我们指定了 aWeekAgo 名称,并将其设置为等于 timespan 函数的输出,该函数返回 datetime 值 。 然后,我们使用分号终止了 let 语句。 现在,我们有一个名为 aWeekAgo 的新变量,该变量可用于查询中的任何位置。

正如我们刚才所提到的,可以使用 let 语句来接收整个查询并为结果指定一个名称。 由于查询结果为 tabular 表达式,可用作查询的输入,因此可以将此命名结果视为表,以便在其上运行另一个查询。 下面是对上一个示例进行的细微修改:

let aWeekAgo = ago(7d);
let getSignins = SigninLogs
| where TimeGenerated >= aWeekAgo;
getSignins

在此示例中,我们创建了第二个 let 语句,并将整个查询包装到一个名为 getSignins 的新变量中 。 与之前一样,我们使用分号终止了第二个 let 语句。 然后,我们在最后一行调用该变量,这将运行查询。 请注意,我们可以在第二个 let 语句中使用 aWeekAgo 。 这是因为我们在上一行中指定了它;如果我们交换 let 语句,让 getSignins 先出现,则会收到错误 。

现在,我们可以使用 getSignins 作为另一个查询(在同一窗口中)的基础:

let aWeekAgo = ago(7d);
let getSignins = SigninLogs
| where TimeGenerated >= aWeekAgo;
getSignins
| where level >= 3
| project IPAddress, UserDisplayName, Level

Let 语句在帮助组织查询时可提供更强大的功能和灵活性。 Let 可以定义标量值和表格值,以及创建用户定义的函数。 在组织可能要进行多个联接的更复杂的查询时,它们真的非常有用。

后续步骤

虽然本文中的介绍并不深入,但你现在已经了解了必要的基础知识,并且我们已经介绍了在 Microsoft Sentinel 中完成工作最常使用的内容。

Microsoft Sentinel 高级 KQL 工作簿

利用 Microsoft Sentinel 中的 Kusto 查询语言工作簿 - Microsoft Sentinel 高级 KQL 工作簿。 它提供了日常安全操作中可能遇到的许多情况的分步帮助和示例,并且指出了许多现成的分析规则、工作簿、搜寻规则示例,以及使用 Kusto 查询的更多元素。 从 Microsoft Sentinel 中的“工作簿”边栏选项卡启动此工作簿。

高级 KQL Framework 工作簿 - 助力你精通 KQL 是一篇优秀的博客文章,展示了如何使用此工作簿。

更多资源

请参阅此学习、培训和技能资源集合,以拓展和深化 Kusto 查询语言方面的知识。