IRQL —— 事件响应查询语言
从选择器、提取器和丰富器构建块中组合IRQL函数管道。IRQL将原始的KQL安全表包装在意图明确、可组合的函数背后,使分析人员(以及LLM)无需记忆模式、集群位置或连接键即可表达狩猎意图。
激活触发器
当用户出现以下情况时使用此技能:
- 明确提及IRQL、
Get_*、Extract_*或Enrich_*函数 - 说“使用IRQL”或“写一个IRQL查询”
- 请求使用已知IRQL选择器构建可组合的狩猎管道
不要 为通用的安全查询(例如“查找失败的登录”)激活此技能,除非用户明确要求使用IRQL。请将这些查询路由到 azure-kusto。
不是自然语言到IRQL的转换器。 此技能负责组合IRQL函数管道,并可处理直接映射到已知选择器和简单过滤器的基本自然语言请求。对于通用的NL到KQL或NL到IRQL转换,请使用专用的查询生成技能(可单独获取)。
IRQL函数预检
在生成管道之前,验证目标数据库上是否可用IRQL:
.show functions
| where Name startswith "Get_" or Name startswith "Extract_" or Name startswith "Enrich_"
| project Name
如果未找到IRQL函数,请告知用户IRQL未部署到目标数据库,并建议改用 azure-kusto 进行原始KQL查询。IRQL函数是前提条件——此技能不会部署基础IRQL选择器。
IRQL是什么
IRQL是基于KQL的函数式方言。它提供:
- 统一模式 —— 不同的安全表映射到一致的列名,无论底层数据源如何
- 可组合性 —— 小函数通过
| invoke链接,从简单步骤构建复杂狩猎 - 可移植性 —— 相同的IRQL管道可在不同集群/数据库上工作;只需重新指向
Get_*原语
IRQL不是一种单独的语言。它是你可以调用的KQL函数。任何有效的KQL都可以与IRQL函数一起使用。
部署IRQL
IRQL函数是存储的KQL函数(.create-or-alter function)。它们必须已部署到目标数据库,此技能才能生成管道。
公共示例集群(函数预部署):
- 集群:
https://kc7001.eastus.kusto.windows.net - 数据库:
ValdyTimes、JoJosHospital
要将IRQL移植到新的集群/数据库,请创建 Get_* 选择器,将源表投影到统一模式(列名如下),然后部署提取器和丰富器。只要输入模式匹配,提取器和丰富器无需修改即可工作。
函数目录
1. 选择器 —— Get_*
返回已投影、模式统一的源表视图。默认使用最小形式;需要额外列时使用 _All。
| 函数 | 列 |
|---|---|
Get_Event_Authentication | EnvTime、Hostname、ClientIp、Username、Result |
Get_Event_Authentication_All | + Description、UserAgent、PasswordHash |
Get_Email | EnvTime、EmailSender、EmailRecipient、Subject、Url |
Get_Email_All | + ReplyTo、Verdict |
Get_Employees | Name、ClientIp、Email、Username、Hostname、Role |
Get_Employees_All | + HireDate、UserAgent、Domain |
Get_Event_FileCreation | EnvTime、Hostname、Filename、Path |
Get_Event_FileCreation_All | + Username、Sha256、ProcessName |
Get_Event_NetworkInbound | EnvTime、ClientIp、Url |
Get_Event_NetworkInbound_All | + Method、UserAgent、StatusCode |
Get_Event_NetworkOutbound | EnvTime、ClientIp、Url |
Get_Event_NetworkOutbound_All | + Method、UserAgent |
Get_Dns_All | EnvTime、Domain、ClientIp |
Get_Event_Process | EnvTime、ProcessCommandLine、ProcessName、Hostname、Username |
Get_Event_Process_All | + ParentProcessName、ParentProcessHash、ProcessHash |
Get_SecurityAlerts_All | EnvTime、AlertType、Severity、Description、Indicators |
Get_Network_Connection_All | EnvTime、SourceIp、SourcePort、DestinationIp、DestinationPort、Protocol、Bytes |
2. 提取器 —— Extract_*
从现有列派生新列。在选择器之后调用。
| 函数 | 输入列 | 新增列 |
|---|---|---|
Extract_Email_Sender_Domain(T) | EmailSender | Domain |
Extract_Employee_Firstname(T) | Name | Firstname |
Extract_Event_Network_Domain(T) | Url | DomainName |
3. 丰富器 —— Enrich_*
左连接辅助函数,从相关表附加上下文信息。
| 函数 | 键列 | 丰富内容 |
|---|---|---|
Enrich_Event_Authentication_Username(T) | Username | 用户的身份验证事件 |
Enrich_Ip_Employee(T) | ClientIp | 基于IP的员工身份 |
Enrich_Username_Employee(T) | Username | 基于用户名的员工身份 |
Enrich_Ip_Domain(T) | ClientIp | 解析到IP的DNS域名 |
Enrich_Ip_Event_NetworkOutbound(T) | ClientIp | 来自IP的出站网络 |
Enrich_Ip_Network_Connection(T) | ClientIp | 来自IP的网络流 |
4. 外部丰富
| 函数 | 来源 | 要求 |
|---|---|---|
Enrich_Sha256_VirusTotal(T) | VirusTotal文件报告 | API密钥 + 回调策略 |
Get_CISA_KEV() / Enrich_CISA_KEV(T) | CISA KEV目录 | 回调策略 |
组合规则
选择器 -> 提取 -> 过滤 -> 丰富 -> 汇总/投影
- 以选择器开头:
Get_Event_Authentication、Get_Email等。 - 提取 派生字段:
| invoke Extract_Email_Sender_Domain() - 过滤 出信号:
| where Result == "Failed Login" - 丰富 上下文:
| invoke Enrich_Username_Employee() - 汇总 / 投影 答案
始终在步骤之间使用管道符(|)。提取器和丰富器使用 | invoke FunctionName()。
查询生成指南
- 除非需要额外列,否则使用最小选择器 -> 然后使用
_All - 在丰富器之前链接提取器(提取器添加丰富器可能依赖的列)
- 尽可能早地放置
where过滤器 - 使用
summarize进行聚合,使用project进行最终列选择 - 以
order by+take结束以限制输出
示例
有关更多提示和完整示例,请参见 references/EXAMPLES.md。
暴力破解检测
Get_Event_Authentication
| where Result == "Failed Login"
| summarize FailedCount = count() by Username
| where FailedCount > 19
| invoke Enrich_Username_Employee()
| project Username, Name, Role, Email, FailedCount
| order by FailedCount desc
按收件人级别进行钓鱼分类
Get_Email
| invoke Extract_Email_Sender_Domain()
| project EnvTime, EmailSender, Domain, Username = EmailRecipient, Subject, Url
| invoke Enrich_Username_Employee()
| extend Seniority = case(
Role has_any ("CEO", "Chief", "Director", "VP", "President"), 3,
Role has_any ("Manager", "Lead", "Senior"), 2,
1)
| summarize
TotalEmails = count(),
SeniorityScore = sum(Seniority),
Recipients = make_set(Name, 50),
DistinctRecipients = dcount(Username)
by Domain
| where DistinctRecipients >= 2
| order by SeniorityScore desc
| take 20
从指标进行后渗透横向
let victims =
Get_Event_FileCreation_All
| where Filename has "<INDICATOR>"
| distinct Hostname;
Get_Event_Process
| where Hostname in (victims)
| where ProcessCommandLine has_any ("rundll32", "regsvr32", "powershell", "systeminfo")
| project EnvTime, Hostname, Username, ProcessName, ProcessCommandLine
| order by EnvTime asc
结合身份信息的可疑出站流量
Get_Event_NetworkOutbound
| invoke Extract_Event_Network_Domain()
| where DomainName has_any ("<SUSPICIOUS_DOMAIN_1>", "<SUSPICIOUS_DOMAIN_2>")
| invoke Enrich_Ip_Employee()
| project EnvTime, Name, Role, DomainName, Url, ClientIp
| order by EnvTime desc
外部IP身份验证异常
Get_Event_Authentication_All
| where not(ClientIp startswith "10.") and not(ClientIp startswith "192.168.")
| summarize
Attempts = count(),
Failures = countif(Result == "Failed Login"),
Users = make_set(Username)
by ClientIp
| order by Failures desc
| take 20
使用的MCP工具
| 工具 | 用途 |
|---|---|
kusto_query | 在Kusto数据库上执行IRQL管道 |
kusto_table_schema_get | 发现可用的表和列 |
kusto_cluster_list | 列出可用的ADX集群 |
kusto_database_list | 列出集群中的数据库 |
在Kusto Explorer中打开查询(仅限Windows)
可选便捷功能。 默认工作流是在聊天中输出KQL,让用户手动复制到Kusto Explorer或VS Code Kusto扩展中。自动启动仅为选择加入。
始终在聊天响应中输出完整的KQL查询,并明确标记步骤1(连接)和步骤2(查询):
// 步骤1:连接到集群(如果已连接,请跳过)
// 示例:取消注释以连接到KC7训练集群
// #connect cluster('kc7001.eastus.kusto.windows.net').database('ValdyTimes')
// 或替换为您自己的集群:
// #connect cluster('<YOUR_CLUSTER>').database('<YOUR_DATABASE>')
// 步骤2:运行以下查询
<KQL_QUERY>
如果用户要求保存或在Kusto Explorer中打开,请按照 references/KUSTO_EXPLORER_LAUNCH.md 中的步骤操作。关键规则:
- 在写入文件或启动可执行文件之前,使用
ask_user进行确认 - 在聊天中显示文件内容,以便用户在打开之前查看
- 绝不使用shell插值或here-string——通过
Set-Content/Add-Content写入文件 - 绝不将查询编码到浏览器URL中
- 在macOS/Linux上,保存
.kql文件,并建议使用VS Code Kusto扩展或ADX Web Explorer - 关于IRQL数据的图形可视化,请参见
azure-kusto-graph和azure-kusto-irql-graph