3、日志格式基础:CEF格式详解

聊到日志标准化,CEF 是个绕不开的格式。我最早接触它是在给某银行做 SIEM 对接的时候,当时各家设备厂商的日志五花八门,有的用竖线分隔,有的用空格,还有的自定义 JSON 结构。后来发现 ArcSight 定义的 CEF 格式居然被这么多厂商支持,说实话,有点相见恨晚的感觉。

CEF 的全称是 Common Event Format,中文叫通用事件格式。它最早由 ArcSight 提出,现在已经是安全设备和日志管理领域的“国际普通话”了。你想想看,不同厂商的设备——防火墙、IDS、IPS、WAF、EDR——如果都用同一种格式输出日志,SIEM 平台解析起来会有多省事。CEF 做的就是这件事。

CEF 格式的起源与设计理念

为什么会有 CEF?

在 CEF 出现之前,每个厂商都按自己的习惯写日志。有的用逗号分隔,有的用制表符,字段顺序还不固定。我记得有一次处理几万台交换机的 syslog,光是写正则解析就花了两周,结果换设备型号又得改。后来接触 CEF 才发现,原来早在 2000 年代初 ArcSight 就已经在推动这件事了。

CEF 的设计思路很直接:

  • 固定前缀:用 CEF:版本号|设备厂商|设备类型|设备版本|签名ID|名称|严重级别 作为必填头部,格式统一。
  • 键值对扩展:头部之后跟随一系列 key=value 格式的扩展字段,自由度高,新字段不用改结构。
  • 竖线分隔:使用管道符 | 分隔头部字段,空格分隔头部与扩展字段。

说白了,CEF 就是用固定的“身份证信息”加上灵活的“附加属性”,既保证了基本字段的确定性,又留下了扩展空间。这在当年的日志格式设计中算是很有远见的做法。

CEF 字段结构详解

CEF 日志的标准格式如下:

CEF:Version|Device Vendor|Device Product|Device Version|Signature ID|Name|Severity|[Extension]

每个字段都有明确语义,我建议你记住这张表:

字段位置 字段名 说明 示例值
1 Version CEF 版本号,最新常用 0 0
2 Device Vendor 设备厂商,不能含竖线 Cisco
3 Device Product 产品名称 ASA
4 Device Version 设备软件版本 9.12.4
5 Signature ID 事件签名标识,唯一 106023
6 Name 事件名称,可读性强 Deny TCP
7 Severity 严重级别,0-10 5
小提示:Severity 的值并非所有厂商都严格遵守0-10,有些直接用字符串如“High”“Low”。解析时最好做一步归一化处理。

头部字段用竖线分隔,扩展部分紧跟一个空格开始。这里有个容易踩的坑:如果某个字段本身包含竖线(比如设备版本号里有人写了描述性文字),必须用反斜杠转义 \|。我在项目中遇到过某家硬件厂商在 Device Product 内部放了“FW|IPS”,结果所有解析代码都断了——后来排查才发现是转义没处理好。

扩展字段机制

扩展字段是 CEF 的精华,它让日志能携带任意上下文信息。格式是所有标准扩展字段用 key=value 表示,多个字段之间用空格分隔。如果 value 包含空格,必须用反斜杠转义空格或直接引号包裹——不过实践中更常见的是直接转义空格。

ArcSight 定义了一套标准扩展键,常用如下:

  • src / dst:源/目标 IP
  • spt / dpt:源/目标端口
  • proto:协议(tcp/udp/icmp)
  • srcMac / dstMac:MAC 地址
  • outcome:结果(允许/拒绝/失败)
  • request:HTTP 请求路径
  • filePath / fileName:文件相关
  • act:操作类型(login, read, delete)
  • msg:描述信息
  • cs1Label / cs1:自定义字段对(厂商可自行定义)

为什么需要自定义字段?

因为标准键不可能覆盖所有场景。我记得对接某款 NGFW 时,它要传递“应用层协议”和“威胁等级”,标准扩展里没有对应键。按照 CEF 规范,厂商使用 cs1Label=AppProtocol cs1=HTTPS 来实现自定义扩展。解析器则通过 cs1Label 来识别这个值的实际含义。

注意:自定义字段的命名规范非常松散。你有可能遇到同一含义在不同设备上用不同 Label 表示,比如“威胁等级”可能是 cs2Label=ThreatLevel 也可能是 cs5Label=SeverityLevel。一个健壮的解析器必须提供字段映射配置功能,不能硬编码。

完整示例解析

来一个真实的 CEF 日志例子,我当年调防火墙日志时截取的:

CEF:0|Cisco|ASA|9.12.4|106023|Deny TCP|5|src=10.0.1.100 spt=443 dst=192.168.1.50 dpt=56789 proto=tcp outcome=Deny msg=Deny\ TCP\ from\ 10.0.1.100:443\ to\ 192.168.1.50:56789\ flagged\ as\ malicious cs1Label=ThreatID cs1=1001

我们来逐段拆解:

  1. 头部固定部分:版本 0,设备厂商 Cisco,产品 ASA,版本 9.12.4,签名 ID 106023,事件名 Deny TCP,严重级别 5。
  2. 扩展字段:
    • src=10.0.1.100 spt=443 源 IP 和端口
    • dst=192.168.1.50 dpt=56789 目标 IP 和端口
    • proto=tcp 协议
    • outcome=Deny 防火墙行为是拒绝
    • msg=Deny TCP from 10.0.1.100:443 to 192.168.1.50:56789 flagged as malicious — 注意空格被反斜杠转义了
    • cs1Label=ThreatID cs1=1001 自定义字段,表示威胁 ID

解析这段日志时,我通常会先按第一个空格将字符串切分为头部和扩展两部分。头部再用竖线分割成7个字段。扩展部分则用空格分割键值对,注意必须考虑 value 内被转义的空格——不能简单用 split(' ')。正确的做法是逐字符扫描,遇到反斜杠跳过下一个字符。

我的建议:不要自己手写解析器。Python 的 cefevent 库或 pycef 都能直接处理标准 CEF,但如果你要解析非标准实现(比如某些厂商在扩展字段里混入了多余空格),最好还是基于 shlex 模块做分词,处理转义更可靠。

避坑指南

CEF 看似简单,实际对接时坑不少。我把自己踩过的坑列出来,希望能帮你少走弯路:

  • 竖线转义缺失:前面提到过,设备信息中可能含竖线。如果没有转义,分割字段数就会错。我曾经因为一个版本号字符串里带了竖线,导致日志被错误解析成两条。
  • 扩展字段值包含空格未转义:有些厂商不规范,msg 字段直接写空格的原始文本,导致解析器把后半部分当作新键值对。我建议解析时做两步:先用正则定位到 msg= 后面的内容,再用引号或转义规则提取。
  • 大小写问题:扩展键是大小写敏感的。标准键全小写,但某些厂商用大写如 Src=。统一在解析时转为小写可减少麻烦。
  • 自定义字段命名冲突:同一个 cs1 在不同设备上可能代表不同含义。全局性 SIEM 平台必须维护一个厂商+设备与 Label 的映射表。
严肃警告:在生产环境里,你几乎不可能遇到100%符合标准的 CEF 日志。ArcSight 的官方规范是一回事,各个厂商的具体实现是另一回事。测试阶段务必拿真实设备日志做验证,不要迷信厂商文档。我曾在某知名 WAF 的日志里发现了完全没有转义空格的非法 CEF,那个厂商的工程师后来承认是 SDK 实现有 Bug。

CEF 日志解析流程

下面这张图概括了 CEF 日志从接收、分割到字段映射的完整流程,算是我多年调试经验的一个浓缩。

CEF 日志解析流程 原始日志字符串 按首个空格分割为两段 按竖线分割为7字段 逐键值对解析(处理转义) 字段名标准化(小写) 合并自定义字段映射表 输出结构化事件对象 注:扩展解析时必须处理 \\ 转义

简单总结一下核心步骤:先分割,再各自解析,最后合并成结构化字典。如果你遇到解析器报错,八成是扩展部分出问题了。检查一下 value 里有没有未转义的空格或竖线,这是最常见的故障点。

好了,CEF 格式就讲到这里。下一块我们要聊的是另一个常见格式 LEEF——它跟 CEF 很像但细节又不一样。不过那是后面的事了,先把基础打牢再说。