ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从散落告警到完整攻击叙事:关联分析系统设计与落地

从散落告警到完整攻击叙事:关联分析系统设计与落地 如果你在一家中等规模的公司做安全运营大概率会遇到这个场景告警平台一夜之间堆了几千条告警打开每条看都是孤零零的事件有的命中威胁情报有的只是端口扫描看不出彼此联系。而攻击者可能早就用一次钓鱼、一次横移、一次外传走完了整条链路。关联分析这个词在不同领域含义差别很大做生物信息学的人想到的是全基因组关联分析做安全的我们想到的则是攻击链路展示——把散落的单点告警还原成一条有头有尾的完整攻击故事。这套系统我从模型设计到可视化落地做过好几轮中间踩了不少坑这篇把完整思路和实现细节复盘一遍。1. 单条告警是一颗散落的弹壳关联分析要解决什么1.1 一个让我彻底改变看法的告警场景早些年我做安全运营的时候最痛苦的不是没有告警而是告警多到没人看得过来。某天早上打开平台里面有这么几条09:12邮件网关员工收到一封主题为“2024Q4_Bonus.xlsm”的带宏附件邮件09:13终端EDR该员工的办公PC上Excel进程拉起了一个powershell进程09:14DNS日志该PC解析了一个从未见过的域名并建立了外联09:26域控认证日志从该PC对域控发起了一串密码尝试前面五十次失败最后一次成功09:42出口流量域控主动向一个境外IP持续发送压缩数据。单独看第一条可能是普通垃圾邮件第二条可能是运维脚本第三条可能是误报第四条像是暴力破解第五条又像是异常上传。但如果把五条按时间和实体排开你会发现这是一个标准的多阶段攻击投递、执行、命令控制、横向移动、数据外传。问题在于当时的告警平台里它们没有任何关联分散在邮件网关、EDR、DNS日志、认证日志、流量分析五个独立模块里。这就是关联分析天然要解决的命题让数据自己把这条链路“串”出来。攻击者在一次攻击里会在不同设备上留下痕迹而这些设备各自只能看到自己那一小段视野。只有把跨数据源、跨时间线的告警拼到一起攻击者的完整路径才会从一团噪音里浮现出来。1.2 日志检索与关联分析的本质区别很多团队一开始会觉得这事用日志检索也能干。毕竟ElasticsearchES足够强我可以把五个模块的日志全部收进来然后手动或者写固定查询把相关事件捞出来。理论上可以实际上走不通。日志检索是“人工假设驱动”的。你必须先猜测IP之间可能有关系才知道去搜哪几个字段。问题在于真实攻击里的后续跳板往往是你猜测之外的节点。分析师的经验决定了能发现多少攻击路径而经验本身就是最大的瓶颈。关联分析是数据驱动的它不依赖人先给出假设而是通过实体和时间的匹配自动把两条原本不相干的告警“发现”为同一条攻击链路的片段。打个比方。日志检索是拿对讲机问另一个保安“你那边是不是有个戴帽子的人经过了”然后自己跑过去看监控录像。关联分析是直接在总控室的大屏上把这个戴帽子的人从进门到进电梯的每一帧轨迹自动连成一条线。一个人看单帧画面永远看不出完整路线但把轨迹拉成线之后路线本身会说话。这也是“攻击链路展示”这个需求的核心意义所在——展示的本质不是画一张好看的图而是把分析维度从“单事件”提升到“完整叙事”。有了叙事一个初级分析师也能看懂攻击者从哪里进来、经过了哪些资产、最终要干什么。1.3 攻击链路系统要交付的三大难题这套系统从立项到可用我总结下来绕不开三座山第一是数据孤岛。不同安全设备对同一个事件的描述完全不一样。同一个源IP有的字段叫src_ip有的叫source_ip有的叫sip时间有的用字符串有的用时间戳。不解决这个问题甚至没法判断两条告警里的“同一个IP”是不是同一个IP。第二是告警风暴。生产环境里的告警量级不是几百条而是几千万条。如果关联逻辑不先做降噪和聚合结果就是图里塞满了边最后呈现出来的链路密密麻麻根本没法看。这一步做得不好关联分析反而会把告警风暴进一步放大成“链路风暴”。第三是可视化信息密度。安全分析师要看细节管理层要看结论。一张图同时满足两者很难链路太粗则没有证据价值链路太细则没有人能看懂。这三座山对应了后文讲的三块内容模型设计解决“怎么关联”数据处理解决“哪些数据能进关联”可视化解决“关联出来怎么让人看懂”。2. 实体、事件与时间窗攻击链路模型的底层设计2.1 实体归一化IP、域名、文件、账号如何成为图的节点刚开始设计这个系统的时候我犯过一个很常见的错误试图用“告警”作为关联分析的基本单元。结果就是图上一堆告警节点关联逻辑只能靠人工配置告警之间的跳转关系维护成本极高根本推不到规模。后来才换过思路来。攻击链路的图模型里节点不能是“告警”而应该是攻击者操作所针对的对象主机IP、外连域名、恶意文件、登录账号、进程。告警本身则是连接两个实体的一条边描述“这个实体对那个实体做了什么”。举个例子。有一条告警是“内网IP 10.10.8.21对10.10.8.55发起445端口扫描”在链路模型里它不是单个节点而是被拆成源IP实体10.10.8.21、目标IP实体10.10.8.55中间一条事件类型的边“扫描探测”。这样做的好处是当另外一条告警“10.10.8.55执行了powershell”进来时两条告警共享实体10.10.8.55就自然产生了一个连接点——你不用写任何关联配置两者的关系在图里已经存在了。实体归一化要做的事就是给每个实体建立唯一的ID。这个ID不是原始日志里的字符串而是经过资产库、威胁情报库、主机名映射之后的标准标识。比如同一个内网IP在不同日志来源可能写成10.10.8.55、office-lisa-023、主机名lisa-pc系统要能通过资产库把它们归一到同一个实体上。这一步做不好后面所有的关联都是鸡同鸭讲。2.2 事件类型与时间窗口怎么定义“相关”实体解决了“谁和谁有关系”下一步要解决“什么时候算相关”。我建议事件类型要自定义一套分类体系但粒度别太细。太细了后续建模和可视化都极其痛苦太多了也不好。我最后用的是二十多种核心事件类型覆盖攻击生命周期的主要动作事件类型含义对应阶段标签扫描探测端口扫描、漏洞扫描、目录爆破侦察暴力破解认证失败或批量登录尝试初始访问/横向移动恶意文件落地下载、打开恶意文件初始访问/执行命令执行终端执行了可疑命令或脚本执行权限提升提权、创建特权账号、修改权限权限提升持久化计划任务、自启动、服务安装持久化内网横移同一账号/工具在内网多台主机出现横向移动C2外联与威胁情报命中的域名/IP建立连接命令控制数据外发异常大流量出网、压缩包外传数据外传“相关”的定义我用了两条硬条件两个事件至少共享一个实体并且发生时间差落在时间窗口内。时间窗口的设计要分两层短窗口用5到15分钟捕捉探测到利用这种快速推进长窗口用24小时捕捉从投递到横移的慢速攻击。有一点非常重要时间窗口不是越大越好。窗口拉长确实能关联出更多事件但误关联也会成倍增加。攻击者的确可能潜伏几周再横移但对一个初始版本来说用24小时窗口已经能覆盖绝大多数自动化攻击链先做对再做大。2.3 攻击阶段标签让链路能讲出战术故事有了实体、事件和时间链路还只是一堆节点和边的集合。要让它变成一个“看得懂的故事”还需要给事件打上攻击阶段标签。这部分我建议参照ATTCK的战术阶段来设计但不用追求到“子技术”级的精细化。每个事件类型映射一个阶段标签比如“恶意文件落地”映射到初始访问“C2外联”映射到命令控制。链路图生成之后链路每跨过一个新阶段界面上就把这个阶段高亮一次读者一眼就能看出攻击推进到了哪一步。我见过一些团队在战术标签上过度较真为了一条告警到底是Execution还是Persistence吵半天。实际上链路的阶段标签是给人阅读理解用的不是给学术评审用的。它的核心价值是让男的事能进图建模的时候尽量少。”高一级的粒度更能稳定地支撑跨设备关联。3. 数据地基告警结构化提取与降噪预处理3.1 字段标准化跨设备告警的第一步任何关联分析系统第一步都是跟字段名较劲。真实环境里的告警来源五花八门EDR、防火墙、邮件网关、蜜罐、流量探针、认证日志……它们对同一个字段的命名方式基本处于“各写各的”状态。我建过一张很长的映射表把来自不同设备、同义不同名的字段全部映射到一个标准schema上。简单展示几个典型对照标准字段EDR来源防火墙来源认证日志来源src_ippidsrc_ipclient_ipdst_iptargetdst_ipserver_ipevent_timeevent_timetimestampDateTimeuser_nameusersrc_usersamAccountName标准字段不要做得太细够用即可。我当时只保留了十来个核心字段源IP、目的IP、源端口、目的端口、协议、时间、源账号、目标账号、文件哈希、域名、进程名、事件类型、原始日志ID。这些字段覆盖了关联分析需要的全部维度多余的字段全部丢进一个json扩展字段里原样保留关键时候还能回溯。时间字段特别提醒一下统一成epoch毫秒加ISO8601字符串两种格式。只用字符串会在窗口滑动和排序时浪费大量性能只用毫秒值则在排查问题时肉眼根本看不出几点几分。两种都保留排序用long展示用字符串。3.2 聚合去重与置信度富化告警进入关联引擎之前必须先做一轮聚合。不然一天几千万条告警打进去图直接炸掉。聚合并不能把事件丢了而是要在事件之间去除“重复”。比如一台主机被端口扫描IDS可能产生几千条几乎相同的告警。这些不需要逐条进图——我会把相同源IP、相同目标IP、相同事件类型、发生时间落在同一分钟内的告警合并为一条聚合告警同时记录count、首末时间并保留一个原始告警ID列表。这样后续点击链路节点时仍然能展开全部原始告警但图上的边只有一条。置信度富化也很关键。每个事件本身要有一个严重级别但在进入关联计算前我会把威胁情报的附加信息恶意标签、情报来源、命中次数和资产属性主机是否核心资产、责任人挂到实体上。这样后面计算链路置信度时就不需要每个事件临时查库直接读节点上挂的属性就能打分。3.3 基于资产属性的过滤决策很多初版系统垮掉都是因为过滤器设计得太粗暴直接谁都不用。我踩过的坑是第一版把扫描器IP全部拉黑之后监控组过了几天发现一个真正的攻击流量被一起误滤了因为攻击者借用了扫描器所在网段的一个跳板。后来我把过滤改成了三层而且原则上“有标记可审计不悄无声息地丢”。第一层是静态白名单公司自有扫描器、堡垒机、监控探针等固定IP列表。这层过滤最确定但也要留个旁路开关关键时刻可以一键关闭。第二层是动态白名单运维计划任务的时间段和常用脚本签名。动态白名单的核心是周期判断——比如某个主机每天凌晨两点准时批量拉起一批SSH连接这属于基线行为链路计算时不参与。第三层是基于资产权重做弱化低价值资产上的低危告警不参与链路初始化但一旦这条链路蔓延到高价值资产则整条链路上的低危事件全部重新激活作为证据。这三层设计之后能明显减少关联引擎的空转同时不会让真正重要的链路因为一次错误过滤而断掉。4. 关联引擎实现规则编排、时间窗口滑算与图路径发现4.1 三层规则体系单点、序列与链式关联引擎是整个系统的心脏。我在实现时把规则分了三个层次。单点规则最简单一个事件本身命中威胁情报或者高危特征比如某个内网主机直接连了已知恶意域名。这类单点事件是链路的“锚点”往往能标记一条攻击旅程的起点或终点。序列规则处理“先A后B”的因果关系事件A在事件B之前发生两者共享一个实体且时间差在窗口内。比如“恶意文件落地—命令执行—C2外联”就是很典型的序列。序列规则的价值在于把几个相邻阶段绑成一个小片段。序列规则的实现要处理时间上的相对顺序——A和B共享主机实体A的时间必须早于B。链式规则是最终产出链路的层次把多个序列片段通过共享实体拼接成一个完整的有向图。这里我不用复杂的图匹配算法而是用一个“从锚点扩展”的方式先选定一批高置信度的锚点事件比如已经确认的C2外联然后沿共享实体向时间前向回溯它的前因再向前推导它的后果。规则引擎的选型上我的建议是不用重型的Drools这类规则引擎而是直接用代码写Pipeline。规则本质上是“条件判断加动作”用Groovy脚本或者Python脚本描述清楚每一步的过滤条件、时间窗口、拼接规则比堆一堆XML规则文件要可维护得多。规则文件一旦多了排查问题时想死的心都有。我后面版本干脆把规则定义成一段可版本化、可单测的代码类每个规则类对应一种攻击模式。4.2 时间窗口内的实体-事件索引实现关联引擎时最需要注意的性能问题是把所有告警两两比较来建边。这个O(n²)复杂度在告警量上来之后会直接把内存和CPU打爆。我的做法是建实体倒排索引。维护一个哈希表实体ID是keyvalue是一个时间有序的事件指针列表。这样当一条新告警进入窗口时不需要把它和All events比较只需要取出它涉及的所有实体然后翻这些实体各自的事件链表看是否有时间差在窗口内的事件。再配合时间分桶每个实体的事件列表按一分钟一个桶来组织滑动窗口推进时直接移出过期桶。这个设计让“找候选关联事件”的复杂度从O(n)降到近似O(候选数)在一天几千万告警的规模下基本能平稳运行。这里有一个细节实体的事件列表不是无限增长的。内存里每个实体最多保留最近24小时的事件超过的直接淘汰。需要回溯更早期历史时再去查离线存储图引擎只处理在线滑动窗口内的数据。4.3 路径发现算法与深度控制当图关系构建完成后真正“展示攻击链路”的动作从这一步开始从链路图里搜索出有意义的路径。我第一版是用DFS从每个高危事件回溯前驱事件。结果就是在一些告警密集的时间段出现一堆非常相似的路径图上面全是重复边根本没法看。必须加约束。约束条件按重要性排序如下深度限制默认从终点回溯不超过4跳。超过4跳的路径要么是误报要么说明中间有大量未被观测到的环节强行把它展示出来不如不展示。边过滤只保留置信度大于某个阈值的边弱关联边会在路径搜索时被剔掉。阈值初始设置为0.6后面根据误报反馈动态调整。节点去重同一主机在一段时间内反复出现的同类事件边合并成一条带次数和总时长的聚合边。起点启发式优先选择“外部IP实体”“邮件投递事件”“未授权设备”这类节点作为起点终点优先选择“域控”“数据库主机”“备份服务器”这类高价值资产上的事件。路径搜索我用的是基于DFS的改进版边按时间序展开遇到已经访问过的节点且时间更新则不再回溯避免成环。这样搜索出的结果是有向无环的时序路径正好匹配“攻击者从A到B再到C”的叙事结构。5. 可视化叙事把关联结果画成攻击者的完整路径5.1 时序泳道图加拓扑图的双层视图开发到可视化这层最纠结的问题是到底用什么图才能让攻击链路“一眼看懂”。纯拓扑图的问题在于它丢失了时间维度的信息。攻击链路的本质是时序推进如果只画节点和边攻击者到底先打了A还是先打了B画面上根本看不出来。纯时间轴又看不清楚实体之间的关联关系。我们的方案是拆成上下两层视图。上层是横向的时序泳道图横轴是时间纵轴按攻击阶段分道每个事件按发生时间落在对应阶段的行里。这条泳道图展现的是“攻击叙事”的时间节奏——什么时候开始的、中间停顿了多久、最后动作发生在哪个阶段。下层是带密度的拓扑图实体是节点事件是边边宽代表关联强度节点颜色代表实体类型。技术选型上早期用D3.js自己撸后来切到AntV G6原因主要是G6自带布局算法、边交互和聚合折叠能力省了很多造轮子的精力。节点颜色规则我们做了统一约定主机IP用蓝域名/外联IP用紫文件哈希用橙账号用绿核心资产加粗边框。5.2 节点的交互下钻与原始告警回溯一张静态图是没法让分析师信服的。分析师真正需要的是看到一条边能点开它看到支撑这条边的原始证据看到前一个节点能回溯这条路径上每一跳的完整上下文。交互层我们做了三件事。第一点击节点右侧抽屉展示该实体的全部属性IP、所属资产、责任人、情报标签以及与该实体相关的全部事件列表按时间倒序。第二点击边展开这条连接的证据面板列出支撑该边的原始告警ID、来源设备、命中规则、原始日志摘要。这一步特别重要因为关联分析本质上是“由系统提出假设由人来确认”没有证据回溯功能分析师不敢信自动关联的结果。第三全局时间范围滑杆。默认显示24小时窗口分析师可以缩小到1小时只看瞬时动作也可以拉长到7天看慢速潜伏链。有一个小细节值得做图上的每条边都带一个“完整度”小标记。节点颜色太杂、边上事件太少、路径覆盖阶段太少时系统可以自动把线画成虚线提示分析师这条链路证据链不完整需要谨慎判断。这比把所有链路的可信度都画成一个强度好理解得多。5.3 为管理层准备的一页纸攻击故事提供给分析师的图信息量一定很大但管理层不需要看那么多原始证据他们只需要知道是不是真被攻了、攻到哪了、影响哪个资产、要不要联动应急。我们在系统里单独做了“摘要模式”。它会自动从完整链路里提取几个关键信息攻击源、入口方式、第一步动作、攻陷的核心资产、横向移动跳数、最终动作、影响评估。生成的页面是一张纵向的“攻击行程卡”从攻击者进入到最后动作每行一个大阶段图标和一句描述。如果有威胁情报背景还会自动附上一段攻击组织画像例如“该IP曾关联到某类恶意样本传播事件”。这一步自动生成的逻辑不算复杂从已经拼接好的链路里找起点节点、终点节点、含高危事件数最多的路径以及路径上覆盖的阶段标签列表。但它对推动安全团队向上汇报的效率提升非常明显——以前应急响应写报告要手动翻日志理时间线现在系统直接把“故事线”生成好了。6. 实战拦路虎误报、漏报与性能的取舍平衡6.1 误报治理白名单体系的演进关联分析上线第一周我就收到了很多抱怨链路数量不仅没减少反而变多了。原因很简单自动化运维、监控探针、CDN回源这类正常行为在时间窗口和共享实体的规则下被大量拼接成了“伪攻击链路”。印象最深的一次误报某业务在促销活动期间CDN回源节点的连接量陡增。我们的系统把回源源站节点和业务服务器的高频外联误判成了“C2外联加数据外发”生成了好几条高置信度误报链路。这让我意识到单纯加IP白名单不够。白名单体系我迭代了三版。第一版是固定IP名单对付扫描器。第二版加入了“周期基线”记录每个资产每周每天各时段的外联行为习惯形成基线画像。如果一条事件的实体组合和当前时段基线一致它在参与关联计算时权重降为原来的20%。第三版加入了“相似链路聚合”如果同一批实体组合在一周内反复生成结构相同的链路默认这是一条批量脚本行为需要人工确认一次后才进入观察名单。最重要的一点是白名单命中必须留操作日志。分析师点开任何一条被过滤的告警都能看到“因为命中哪个基线、在什么时间被过滤”的记录。这一点在出现疑难争议时能救命。6.2 漏报是设计边界而非事故误报聊完之后必须聊漏报。很多团队追求把“所有攻击”都关联出来结果往往是强行拼接制造出一堆逻辑不通的假链路。我的原则是宁可展示两条断裂链路也不生成一条虚假链路。真实环境里日志总有缺失环节。比如攻击者在内网横移时某台网络设备没有开日志中间一跳就断了。此时系统如果强行把前后片段拼起来就会生成一条缺失中间节点的不可信链路。我们选择的做法是在链路图上保留断点标记明确标出“该路径中间存在未观测到的事件”同时降低这条链路的整体置信度。分析师看到后可以自己去补查那台设备的日志或者干脆把这条链路标记为“待完善”。漏报的系统化治理还有一个关键工具——链路完整度评分。就是一个链路上实际覆盖的阶段标签数量除以理论期望阶段数。覆盖超过5个阶段的链路通常是比较完整的攻击叙事覆盖率只有两三个阶段的大概率只是某个异常片段需要分析师重点核查。6.3 性能优化从全量扫描到索引驱动最后说一下性能。在一天近亿条日志、近千万条告警的环境里如果关联引擎不做优化单机内存命中率会非常难看。我遇到最明显的问题是GC频繁排查下来发现是因为每个事件对象里存了太多String字段。几个立竿见影的优化手段事件对象紧凑化时间戳用longIP用整数封装进程名和域名用字符串池常量。事件对象整体体积从几百字节压缩到几十字节。LRU缓存资产属性实体资产属性不要每个事件查库用LRU缓存顶住热数据缓存命中率基本在95%以上。按实体哈希分片对实体ID做一致性哈希把不同实体的关联计算分到多个worker并行执行每个worker只需要和本地分片内的事件做图构建最后通过一次聚合合并全局路径。依托离线存储做长历史回溯在线内存图只保留24小时内的事件更长历史的关联需求走离线计算结果缓存到结果库中不反复重算。这套优化做完之后单集群在高峰期能稳定支撑每秒几千条告警的实时关联。7. 一条真实攻击链路的完整演示7.1 输入告警几个看似无关的事件说再多理论不如把一个完整案例走一遍。以下是我在测试环境里构造的一条典型钓鱼攻击链路原始告警如下告警序号时间来源事件内容A109:12:33邮件网关员工lisa收到带附件邮件2024Q4_Bonus.xlsm发件人hr-noreplylookalike.exampleA209:12:50终端EDR主机host-lisa-023下载并打开2024Q4_Bonus.xlsm宏自动执行A309:13:02终端EDR主机host-lisa-023上excel.exe拉起powershell.exe命令行含-enc参数A409:13:20出口流量主机host-lisa-023外连域名cdn.oss-update-zone.example命中威胁情报A509:26:44域控认证账号lisa.q从host-lisa-023尝试登录DC01失败50次后成功1次A609:41:52出口流量DC01向198.51.100.87的8443端口持续发送约200MB压缩数据这六条告警分布在四个独立数据源里按设备单独看哪条都可能被当成事件处理掉。7.2 实体抽取、片段拼接与置信度打分进入关联引擎后第一步是实体抽取。A1到A4里抽出的关键实体有文件2024Q4_Bonus.xlsm、进程powershell.exe、主机host-lisa-023、域名cdn.oss-update-zone.example、账号lisa.q。A5里又出现了主机host-lisa-023和账号lisa.q与DC01的关联。这样前四件事和后两件事通过同一主机和同一账号自然衔接无需任何人工干预。拼接过程大致是这样时间窗口24小时内A1、A2、A3共享“主机host-lisa-023”和“文件”实体先连成“投递到执行”的序列片段A4与A3共享主机补上“命令控制”环节A5与前序片段共享主机和账号形成“横向移动”环节A6再通过DC01连接A5形成“数据外传”终点。置信度打分用了一个简单而透明的模型每个事件的基准权重乘以时间衰减因子再乘以资产重要性系数。关键系数举例如下事件类型基准权重时间衰减每30分钟恶意文件落地400.98命令执行500.98C2外联600.97横移成功700.95数据外传800.90DC01是域控资产重要性系数1.5。算下来这条链路的总分超过300分远高于默认上报阈值200分。阶段标签覆盖初始访问、执行、命令控制、横向移动、数据外传共5个环节链路完整度约83%。系统自动判定这条链路需要优先展示。7.3 最终的链路视图与复盘思考分析师最后在界面上看到的是一张图起始节点是发件邮箱和恶意文件中间经过主机、命令控制域名、横向移动到域控终点是外传IP。时间轴上六个点依次排开。点击A4那条边能直接看到DNS解析记录和外联流量的原始日志点击域控节点能看到该资产的关键级别和继任负责人的联系方式。这套系统真正跑起来之后我最大的感受是攻击链路展示能力的下限取决于数据质量上限取决于模型取舍。做一个这样的平台核心不是算法选型多高级而是把“字段标准化”这个最土最累的地基工作做扎实。如果你也在做类似的东西我的建议是先别急着调算法和画图回去花两周时间把告警字段映射表和实体资产库整清楚后面所有的关联逻辑都会顺畅起来。可视化也只是为了让已经存在的因果关系更可读——真正重要的是前面的数据链路能不能把事情说圆。这个内容后续还有很多可以扩展的玩法。比如把链路结果直接联动到SOAR平台触发自动封禁和应急处置又比如把基线和白名单做成持续学习的模型让误报率随着运行时间自动下降。但这些都是在这个地基之上锦上添花的事了。
返回列表