一些使用 KQL for Microsoft Sentinel 的技巧、示例和实例。
Kusto 查询语言是一种用于 Azure Monitor、Azure Data Explorer 和 Azure Log Analytics(Microsoft Sentinel 底层使用的语言)的语言。我一直觉得下面这张关于 KQL 的图非常有用——

我们想要使用 KQL 创建准确、高效的查询,以便从更大的数据集中发现威胁、检测、模式和异常。
以下面的查询为例```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
当我们运行这样的查询时,第一行告诉 Microsoft Sentinel 要在哪个表中查找数据,所以在这种情况下,我们要搜索 SigninLogs 表,这是 Azure AD 登录数据被发送到的地方。你可以[在此处](https://docs.microsoft.com/en-us/azure/sentinel/data-source-schema-reference)查看表列表。
然后 Microsoft Sentinel 会按顺序运行你的查询,也就是说,它会一行一行地执行,直到到达末尾或出现错误。所以下面我们逐行分解这个查询。```kql
SigninLogs
因此,首先我们选择了我们的 SigninLogs 表。```kql SigninLogs | where TimeGenerated > ago(14d)
接下来,我们让 Sentinel 回溯查看此表中过去 14 天的数据。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
接下来,我们要求 Sentinel 仅查找 UserPrincipalName 等于 "[email protected]" 的日志。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0"
然后,我们只查找 `ResultType == 0` 的日志,这些日志表示成功登录 Azure AD。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
接下来,我们只查找登录 Microsoft Teams 的情况。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
Our last line uses the project operator, to return only 4 fields from our logs, so we will only see the TimeGenerated, Location, IPAddress and UserAgent returned from our SigninLogs data.
That is how you build queries, now the basics.
## 基础知识
### 时间基础
Microsoft Sentinel 和 KQL 针对时间筛选进行了高度优化,因此如果你知道要搜索的数据的时间段,应该立即筛选时间范围。例如,检索最近 14 天的日志,然后搜索一个用户名,如下面的查询 -```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
比先搜索用户名再搜索时间段要高效得多,就像这样 -```kql SigninLogs | where UserPrincipalName == "[email protected]" | where TimeGenerated > ago(14d)
KQL 有许多选项可用于查询特定时间段。```kql
SigninLogs
| where TimeGenerated > ago(14d)
如第一个示例所示,这将搜索最近14天。```kql SigninLogs | where TimeGenerated > ago(14h)
你也可以按小时进行。```kql
SigninLogs
| where TimeGenerated > ago(14m)
And minutes.
KQL also supports querying between time ranges -```kql SigninLogs | where TimeGenerated between (ago(14d) .. ago(7d))
这将查找14天前至7天前之间的SigninLogs数据。```kql
SigninLogs
| where TimeGenerated between (ago(14h) .. ago(7h))
在14小时前到7小时前之间。```kql SigninLogs | where TimeGenerated between (ago(14m) .. ago(7m))
And between 14 minutes and 7 minutes ago.
### Where 基础
Where 是一个运算符,你基本上在编写的每个查询中都会使用它。这就是你告诉 Microsoft Sentinel 搜索特定数据的方式。对于 where 运算符来说,语法非常重要。如果我们使用之前相同的示例。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
这将搜索我们的 SigninLogs 表,在过去 14 天内,查找 UserPrincipalName 等于 [email protected] 的精确匹配。在 KQL 中,== 区分大小写,因此如果你搜索 [email protected] 而实际用户名是 [email protected],你将不会得到任何结果。不区分大小写的等效写法是 =```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName = "[email protected]"
这将查找 [email protected] 的任何匹配项,无论大小写如何。
除了 equals,我们也可以使用 contains。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName contains "reprise_99"
这将查找 UserPrincipalName 包含 reprise_99 的任何日志条目,如果您有 [email protected] 和 [email protected] 的数据,它将同时找到两者。contains 运算符不区分大小写,但您可以使用 contains_cs 使其区分大小写。
如果您正在搜索特定模式,可以使用 startswith 或 endswith。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName startswith "reprise_99"
SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName endswith "testdomain.com"
startswith 和 endswith 均不区分大小写,但你可以使用 startswith_cs 或 endswith_cs 使其区分大小写。
如果你要搜索完整的单词(超过四个字符),在 KQL 中可以使用 has 运算符。使用 'has' 比 'contains' 更高效,因为数据已为你建立索引。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has "Teams"
这将查找应用程序显示名称中包含单词 Teams 的任何 SigninLogs,可能包括 "Microsoft Teams" 和 "Microsoft Teams Web Client",两者都满足该查询。
如果要搜索多个单词,可以使用 has_any 或 has_all。```kql SigninLogs | where TimeGenerated > ago(14d) | where AppDisplayName has_any ("Teams","Outlook")
这将返回应用程序显示名称包含 "Teams" 或 "Outlook" 的结果```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName has_all ("Teams","Outlook")
这将返回应用程序显示名称包含“Teams”和“Outlook”的结果。
如果你不知道要在哪些字段中搜索,你也可以使用通配符,这样效率不高,但可能会帮你找到正确的方向。```kql SigninLogs | where TimeGenerated > ago(14d) | where * contains "reprise_99"
这将在 SigninLogs 表中搜索任何包含 reprise_99 的字段。
其中许多选项还支持使用 `!` 来反转查询,以查找条件不成立的结果。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName != "[email protected]"
此查询将查找所有 UserPrincipalName 不等于 [email protected] 的 SigninLogs```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName !contains "reprise_99"
此查询将查找所有 UserPrincipalName 不包含 reprise_99 的 SigninLogs```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where AppDisplayName !has "Teams"
此查询将查找应用程序显示名称不包含"Teams"的 SigninLogs。
Project 允许我们选择在查询中返回哪些列以及它们的顺序。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | where AppDisplayName == "Microsoft Teams" | project TimeGenerated, Location, IPAddress, UserAgent
此查询搜索过去 14 天的 SigninLogs 数据,其中 UserPrincipalname 等于 [email protected],ResultType 为 0,应用程序显示名称等于 "Microsoft Teams",然后对于该查询的每个匹配项,返回 TimeGenerated、Location、IPAddress 和 UserAgent。
我们可以在同一个函数中重命名列。```kql
| project LogTime=TimeGenerated, SigninLocation=Location, IP=IPAddress, Agent=UserAgent
这将返回相同的数据,但将列重命名为 LogTime、SigninLocation、IP 和 Agent。
我们甚至可以借助 project 运算符对输出进行内联操作。```kql | project LocalTime=TimeGenerated+5h, Location, IPAddress, UserAgent
这会返回相同的数据,但会将 TimeGenerated 名称更改为 LocalTime,如果你在该时区工作,则转换为 +5h 时区。
project-away 与 project 相反,将从查询中移除列。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| project-away UserAgent
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| where AppDisplayName == "Microsoft Teams"
在此查询中,我们移除了 UserAgent。请记住,如果你移除了某一列,之后在查询中就无法再访问它了。
Summarize 生成一个表,用于聚合你查询中的内容。Summarize 具有许多底层的聚合函数。如果我们再次使用示例查询,就可以通过 summarize 以各种方式操作结果。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize count() by AppDisplayName
此查询将查找 SigninLogs 表中过去 14 天内与 [email protected] 匹配的所有事件,其中结果为成功(ResultType == 0),然后按应用程序显示名称汇总这些事件。
您可以选择为结果列命名。```kql
SigninLogs
| where TimeGenerated > ago(14d)
| where UserPrincipalName == "[email protected]"
| where ResultType == "0"
| summarize AppCount=count() by AppDisplayName
这会返回相同的数据,但会将返回列的名称更新为 AppCount。
除了总计数之外,你还可以对去重计数进行汇总。```kql SigninLogs | where TimeGenerated > ago(14d) | where UserPrincipalName == "[email protected]" | where ResultType == "0" | summarize DistinctAppCount=dcount(AppDisplayName) by AppDisplayName
这将针对 [email protected] 登录过的每个不同应用程序返回一条记录。