Azure Sentinel多源日志关联:KQL检测规则实战指南

📅 2026/7/26 4:20:28
Azure Sentinel多源日志关联:KQL检测规则实战指南
1. 项目概述从日志孤岛到威胁关联在云安全运营中心SOC待过几年的朋友一定对“日志孤岛”这个词深恶痛绝。防火墙日志里看到一个可疑IP你得手动去服务器日志里翻找对应的登录记录服务器上发现一个异常进程你又得去终端安全EDR的控制台查它的父进程和网络连接。这种在不同控制台之间反复横跳、靠人脑关联线索的日子效率低下不说还极易遗漏关键攻击链。Azure Sentinel作为微软的云原生SIEM安全信息与事件管理和SOAR安全编排、自动化与响应平台其核心价值之一就是打破这种孤岛。它通过统一的Log Analytics工作区将Azure活动日志、Microsoft 365 Defender、第三方防火墙、自定义应用日志等海量数据源汇聚一堂。然而数据汇聚只是第一步真正的“炼金术”在于如何从这堆数据中提炼出真正的威胁信号——这就是KQLKusto Query Language检测规则的用武之地。这个项目我们聚焦于一个高阶且实战性极强的场景编写基于多源日志关联的威胁狩猎检测规则。这不仅仅是写一个查询语句那么简单它代表了一种从被动告警到主动狩猎的思维转变。传统的单源检测规则比如“在安全日志中发现10次失败的登录”已经不足以应对现代攻击。攻击者会进行横向移动使用合法工具Living-off-the-Land其攻击痕迹会分散在身份验证、网络、终端、应用等多个日志源中。我们的目标就是利用KQL强大的关联分析能力将这些分散的、看似无害的“点”串联成一条清晰的“攻击线”从而实现更精准、更早期的威胁发现。简单来说如果你满足以下任一条件这篇实战指南就是为你准备的你是Azure Sentinel的新手管理员想超越基础告警模板构建更智能的检测能力你是一名安全分析师厌倦了在多个控制台间手动关联分析渴望通过自动化提升狩猎效率或者你是一名安全工程师正在为团队设计可复用的、覆盖ATTCK技战术的检测规则库。接下来我将以一个虚构但高度典型的“初始访问-横向移动-数据渗出”攻击链为蓝本手把手带你拆解如何设计、编写和优化一个多源关联的KQL检测规则。2. 核心思路与架构设计在动手写第一行KQL之前我们必须先把设计思路理清楚。一个鲁棒的多源关联检测规则其核心在于“假设驱动”和“数据映射”。2.1 攻击链建模与假设驱动威胁狩猎不是漫无目的地搜索而是基于对攻击者行为TTPs的理解提出具体的假设然后用数据去验证。我们参考MITRE ATTCK框架针对一个常见的攻击场景建立模型攻击链假设攻击者通过暴力破解或密码喷洒T1110获得了一个普通用户账户初始访问。随后他们利用该账户登录到一台虚拟机执行并尝试通过PsExec、WMI或计划任务等工具进行横向移动T1021.002 T1053.005。在成功控制另一台存有敏感数据的服务器后他们使用压缩工具打包数据并通过HTTP/HTTPS或SMB向外传输数据渗出 T1048。基于这个假设我们需要从多个数据源寻找证据点身份验证日志Azure AD SigninLogs / SecurityEvent寻找异常的登录模式如来自陌生地理位置、陌生IP、非常用设备的成功登录。虚拟机活动日志AzureActivity监控可疑的VM管理操作例如在非工作时间启动/停止VM或者由非管理员账户执行这些操作。终端安全日志Microsoft Defender for Endpoint 或 SecurityEvent中的进程创建事件检测PsExec、Mimikatz等黑客工具的执行或异常的子进程生成关系如cmd.exe生成powershell.exe再下载可疑文件。网络流量日志CommonSecurityLog 来自防火墙 或 AzureNetworkAnalytics寻找内部主机间异常的高频连接横向移动或内部主机向外部可疑IP/域名发起的大容量数据上传渗出。2.2 数据源关联逻辑设计如何将这些点关联起来关键在于找到可靠的“连接键”。在安全日志分析中最有效的连接键通常是主机名Computer将活动定位到具体的资产。IP地址IP地址 源/目标追踪网络层面的活动轨迹。用户名Account/UPN追踪身份在攻击链中的使用。时间窗口Time将发生在相近时间段内的可疑事件关联起来构成一个会话。我们的关联逻辑可以设计为一个“漏斗模型”第一层筛选宽泛入口从某个数据源如身份验证日志中筛选出高可疑度的事件集合。例如所有“成功登录但来自风险IP”的会话。第二层关联横向扩展以第一步筛选出的结果中的关键字段如用户名、源IP、目标主机名为线索在其他数据源中查询同一时间段内与这些线索相关的活动。例如用可疑用户名去查询终端安全日志看该用户是否在登录后立即执行了可疑进程。第三层聚合与评分将关联到的所有事件聚合起来根据事件的风险等级、数量、时间密度等维度计算一个综合风险评分。只有评分超过阈值的关联事件集才生成最终告警。这种设计的好处是它允许单个环节的弱信号比如一次来自新IP的登录本身可能不是大问题在与其它环节的弱信号结合后形成强信号该登录后立即发生了横向移动尝试从而显著降低误报提高检测精度。实操心得在设计阶段一定要和日志管理员确认每个所需数据源是否已正确连接到Sentinel工作区并且日志的解析是否正常字段是否齐全、格式是否正确。最尴尬的事情莫过于精心设计了一个关联逻辑结果发现关键字段在日志里是空的或者格式不对。3. KQL核心语法与多表关联技巧KQL是Sentinel的灵魂其语法类似于SQL但为时序和日志数据分析做了大量优化。要实现多源关联你必须掌握几个核心操作符和函数。3.1 基础但至关重要的操作符join与unionjoin这是关联不同表的利器。它根据指定的键将两个表的行匹配起来。// 示例将可疑登录和后续的进程创建事件关联起来 let suspicious_logins SigninLogs | where ResultType 0 // 成功登录 | where RiskLevelDuringSignIn high | project LoginTime TimeGenerated, UserPrincipalName, IPAddress, DeviceId; let process_events SecurityEvent | where EventID 4688 // 新进程创建 | project ProcessTime TimeGenerated, Computer, NewProcessName, SubjectUserName; suspicious_logins | join kindinner ( process_events ) on $left.UserPrincipalName $right.SubjectUserName | where ProcessTime between (LoginTime .. (LoginTime30m)) // 登录后30分钟内的进程join的kind参数很重要inner只返回两边都匹配的行leftouter会返回左边所有行即使右边没有匹配根据你的检测逻辑选择。union将多个结构相似的表上下堆叠起来。常用于从不同数据源查询相同类型的事件比如不同防火墙的日志然后进行统一分析。union isfuzzytrue (CommonSecurityLog | where DeviceVendor Palo Alto Networks), (CommonSecurityLog | where DeviceVendor Fortinet) | where ... // 统一的分析逻辑isfuzzytrue参数能容忍表之间字段的细微差异非常实用。3.2 时间窗口函数让关联更精准安全事件之间的时间关联至关重要。KQL提供了强大的时间处理函数。bin(): 将时间戳按指定间隔如5分钟、1小时分桶是进行时间序列聚合和分析的基础。| summarize EventCountcount() by bin(TimeGenerated, 5m), Computerdatetime_diff(): 计算两个时间点之间的差值。| extend TimeDelta datetime_diff(second, ProcessTime, LoginTime) | where TimeDelta between (0 .. 1800) // 相差在1800秒30分钟内ago(): 获取当前时间之前某个时间点常用于动态时间范围查询。| where TimeGenerated ago(1d) // 查询过去24小时的数据3.3 多步骤查询与let语句复杂的关联查询往往会很长。使用let语句将查询分解为多个逻辑步骤可以极大地提高可读性和可维护性。// 步骤1定义可疑登录 let HighRiskLogins SigninLogs | where ... // 筛选条件 | project LoginTime, User, IP, SessionId; // 步骤2定义可疑进程例如来自互联网IP的进程创建 let SuspiciousProcesses SecurityEvent | where ... // 筛选条件 | project ProcessTime, Computer, User, ProcessName, CommandLine; // 步骤3关联 HighRiskLogins | join kindinner ( SuspiciousProcesses ) on User | where ProcessTime between (LoginTime .. (LoginTime1h)) // 步骤4进一步丰富信息例如加入网络连接数据 | join kindleftouter ( WireData // 或其它网络日志 | where ... ) on Computer // 步骤5最终聚合与输出 | summarize ... by User, Computer, bin(TimeGenerated, 1h)这种结构清晰明了方便调试和后续修改。注意事项join操作特别是涉及大表时可能非常消耗查询资源并影响性能。务必确保join的键字段是经过筛选和project精简后的结果集并且时间范围尽可能缩小。Sentinel对查询的复杂度和返回数据量有限制设计规则时要时刻考虑性能。4. 实战演练编写一个多源关联检测规则现在我们融合以上思路和技巧构建一个具体的检测规则。假设我们要检测“成功爆破登录后利用计划任务进行横向移动”的场景。4.1 步骤一定义规则元数据与查询计划在Sentinel的“分析”页面创建新计划规则。首先填写基本信息名称、描述、相关TTPT1110, T1053.005、严重性中/高。我们的查询计划是从SigninLogs中找出高风险的成功登录例如来自威胁情报馈送中的恶意IP。从SecurityEvent中找出由上述登录用户创建的、可疑的计划任务例如任务名称为随机字符串或指向可疑可执行文件路径。关联两者并确保计划任务创建发生在登录后较短的时间内。可选进一步关联SecurityEvent中的计划任务执行事件EventID 4698或后续的进程创建事件以增强置信度。4.2 步骤二编写核心KQL查询// Part 1: 获取过去24小时内来自已知恶意IP示例的成功登录 let MaliciousIPs datatable(IP:string) [192.168.1.100, 10.0.0.15]; // 实际中应替换为威胁情报馈送 let TimeRange ago(1d); let HighRiskLogins SigninLogs | where TimeGenerated TimeRange | where ResultType 0 // 成功登录 | where IPAddress has_any (MaliciousIPs) // 假设IPAddress字段包含IP | project LoginTimeTimeGenerated, UserPrincipalName, IPAddress, AppDisplayName, DeviceDetail; // Part 2: 获取同一时间段内与计划任务创建相关的事件 // Windows Security Event ID 4698: 计划任务已创建。但更常用的是Event ID 4702计划任务已更新包含创建和修改。 // 我们使用更通用的SecurityEvent表并查找包含“Task Scheduler”和“Created”或“Registered”的事件。 // 注意实际字段名可能因日志收集代理而异此处为示例。 let SuspiciousScheduledTasks SecurityEvent | where TimeGenerated TimeRange | where EventID 4702 // 任务已注册 | where SubjectUserName ! SYSTEM and SubjectUserName ! LOCAL SERVICE // 排除系统账户 | extend TaskName extract(Task Name:\s\t(\S), 1, EventDescription) // 解析任务名 | extend TaskAction extract(Task Content:\s.*Action:.*Executable: (\S), 1, EventDescription) // 解析执行程序 | where isnotempty(TaskName) and isnotempty(TaskAction) // 添加一些简单的启发式规则任务名可疑或执行路径可疑 | where TaskName matches regex [a-f0-9]{16,} // 任务名像随机MD5 or TaskAction has \\temp\\ or TaskAction has \\users\\public\\ | project TaskCreateTimeTimeGenerated, SubjectUserName, Computer, TaskName, TaskAction; // Part 3: 关键关联 - 将可疑登录用户与计划任务创建者关联 HighRiskLogins | join kindinner ( SuspiciousScheduledTasks ) on $left.UserPrincipalName $right.SubjectUserName // 时间关联任务创建在登录后合理时间内例如2小时内 | where datetime_diff(minute, TaskCreateTime, LoginTime) between (0 .. 120) // 丰富输出信息 | extend CustomEntity strcat(UserPrincipalName, on , Computer, created task: , TaskName) | project-rename Account UserPrincipalName, Host Computer | project LoginTime, TaskCreateTime, Account, Host, IPAddress, TaskName, TaskAction, AppDisplayName // 去重一个用户可能在多台机器上创建任务按用户和主机聚合取最早时间 | summarize FirstLoginmin(LoginTime), FirstTaskCreationmin(TaskCreateTime), TaskListmake_set(TaskName), IPListmake_set(IPAddress) by Account, Host | extend Severity case( array_length(TaskList) 2, High, // 创建了多个可疑任务 Medium )4.3 步骤三配置规则参数与事件分组在Sentinel规则编辑界面查询计划将上述KQL粘贴进去。计划根据业务需求设置例如每5分钟运行一次查询过去6小时的数据。事件分组这是降低告警噪音的关键。我们可以选择“将所有事件分组到一个告警中”并按Account和Host字段分组。这样同一个用户在同一个主机上的所有相关活动在设定的时间窗口内如30分钟只会生成一条告警告警详情里会包含所有关联的事件列表。警报详情设置告警标题、描述。标题可以动态化如“Suspicious lateral movement via scheduled task by {Account} on {Host}”。实体映射这是SOAR自动化的基础。必须正确映射账户映射到Account字段。主机映射到Host字段。IP地址映射到IPList字段这是一个数组Sentinel能处理。战术与技术选择MITRE ATTCK中的对应项如“初始访问T1110”、“计划任务/作业T1053.005”。4.4 步骤四设置自动化响应可选但推荐Sentinel强大的SOAR能力可以在这里发挥作用。创建或选择一个Playbook逻辑应用在规则触发时自动执行响应动作例如自动在Microsoft Defender for Endpoint上隔离被入侵的主机Host实体。自动在Azure AD中要求该用户Account实体进行密码重置或临时禁用账户。自动在ITSM工具如ServiceNow中创建工单指派给相应的安全或IT团队。将告警信息发送到Teams或Slack频道通知安全分析师。配置“自动化规则”将我们刚创建的检测规则与这个Playbook关联起来并设置触发条件例如仅当严重性为“高”时自动运行。实操心得在将规则投入生产环境前务必使用“分析”页面的“测试规则”功能用历史数据跑一遍查询检查返回的结果是否符合预期事件分组和实体映射是否正确。最好设置一个初始的“低”严重性并观察几天根据误报情况调整查询逻辑的阈值和筛选条件这是一个“调优”的必经过程。5. 性能优化与查询调试技巧一个写得不好的KQL查询可能会拖垮你的Log Analytics工作区导致查询超时或成本飙升。以下是一些关键的优化技巧5.1 优化查询性能尽早过滤减少数据量在查询的最开始使用where语句加上时间范围TimeGenerated ago(1h)和其他最严格的筛选条件。Log Analytics是列式存储早期过滤能极大减少后续步骤需要处理的数据行。明智地使用project尽早使用project或project-away只保留必需的字段。尤其是在join之前对两个表都进行字段精简能大幅提升join性能。避免在where子句中对字段进行函数计算例如where tolower(DeviceVendor) microsoft会导致全表扫描性能很差。如果可能在数据引入时进行规范化处理或者使用has、has_cs区分大小写等运算符。慎用contains多用hashas运算符性能优于contains因为它作用于整个词条。foo bar has foo为真但foo bar has oo为假。contains是子字符串匹配更慢。利用汇总summarize和分箱bin对于需要聚合统计的查询使用summarize ... by bin(TimeGenerated, 5m)比返回所有原始行要高效得多。5.2 调试与排查使用print语句进行变量检查在复杂查询中可以用let定义中间变量然后用print输出其内容检查数据是否符合预期。let myIPList MaliciousIPs | take 5; print myIPList分步执行就像我们之前用let将查询分块一样在Logs页面可以分段执行查询先确保第一部分HighRiskLogins能返回合理结果再逐步加入关联部分。关注查询统计信息在Logs查询结果下方Sentinel会显示“查询统计信息”包括处理的数据量GB、返回的行数、查询耗时。这是评估查询效率的黄金指标。如果一次查询处理了上百GB数据你就需要考虑优化了。理解“空结果”关联查询返回空结果不一定代表没有威胁。可能是关联键不匹配比如用户名格式不同userdomain.comvsDOMAIN\user。需要使用trim()、replace()或extract()函数进行规范化。时间窗口设置太窄。需要适当放宽between的范围。数据源本身没有收集到相关事件。需要检查数据连接器状态和日志收集策略。6. 进阶动态列表与威胁情报集成要让检测规则更智能离不开外部情报。Sentinel支持“威胁情报”平台集成和“监视列表”。6.1 使用监视列表Watchlist作为动态名单我们可以将恶意IP列表、可疑域名、高权限服务账户名单等维护在监视列表中然后在KQL中动态引用。这样更新名单后所有引用该名单的检测规则都会立即生效无需修改KQL代码。在Sentinel中创建“监视列表”例如名为HighRiskIPs包含IPAddress字段。在KQL查询中引用它let KnownBadIPs (_GetWatchlist(HighRiskIPs) | project IPAddress); SigninLogs | where IPAddress in (KnownBadIPs) ..._GetWatchlist是内置函数用于获取监视列表内容。6.2 集成威胁情报Threat Intelligence馈送Sentinel可以接入来自AlienVault OTX、Palo Alto Networks MineMeld等源的威胁情报指标IOCs。这些IOC会自动出现在ThreatIntelligenceIndicator表中。我们可以直接关联此表进行检测// 关联登录日志与威胁情报中的恶意IP SigninLogs | where ResultType 0 | join kindinner ( ThreatIntelligenceIndicator | where Active true | where ThreatType malicious-ip | project IndicatorNetworkIP // 假设威胁情报中的IP字段是NetworkIP ) on $left.IPAddress $right.Indicator这种方式比静态监视列表更强大因为情报是自动更新和丰富的。7. 规则维护与生命周期管理编写规则只是开始持续的维护同样重要。建立规则文档为每个自定义规则创建简单的文档说明其目的、检测逻辑、关联的数据源、调优历史。这有助于团队知识传承和故障排查。定期审查误报/漏报将告警分类为“真阳性”、“误报”、“需调查”。定期分析误报原因是查询逻辑过宽、数据噪声还是正常业务行为根据分析结果优化查询。对于漏报思考是否需要引入新的数据源或调整检测逻辑。性能监控定期查看“工作簿”中的“规则性能”报告关注哪些规则查询耗时最长、处理数据量最大。对性能瓶颈规则进行优化。版本控制虽然Sentinel界面直接编辑规则很方便但对于重要的生产规则建议将KQL查询代码保存在Git等版本控制系统中记录每次变更的缘由便于回滚和审计。退役过时规则当业务环境变化、攻击手法升级或数据源格式更改时有些规则可能不再有效或产生大量误报。需要建立流程定期评估并退役这些规则。编写多源日志关联的KQL检测规则是一个将安全理念、攻击者知识、数据工程和工具使用深度融合的过程。它没有一成不变的“银弹”查询需要你不断根据自身的环境、面临的威胁和可用的数据源进行迭代和调优。从模仿一个简单的关联规则开始逐步理解每一行代码背后的意图然后尝试为自己的环境量身定制规则是掌握这项技能的最佳路径。当你的第一条自定义关联规则成功捕获到一次真实的攻击尝试时那种成就感会告诉你所有的努力都是值得的。