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 |
头部字段用竖线分隔,扩展部分紧跟一个空格开始。这里有个容易踩的坑:如果某个字段本身包含竖线(比如设备版本号里有人写了描述性文字),必须用反斜杠转义 \|。我在项目中遇到过某家硬件厂商在 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 来识别这个值的实际含义。
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
我们来逐段拆解:
- 头部固定部分:版本 0,设备厂商 Cisco,产品 ASA,版本 9.12.4,签名 ID 106023,事件名 Deny TCP,严重级别 5。
- 扩展字段:
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 的映射表。
CEF 日志解析流程
下面这张图概括了 CEF 日志从接收、分割到字段映射的完整流程,算是我多年调试经验的一个浓缩。
简单总结一下核心步骤:先分割,再各自解析,最后合并成结构化字典。如果你遇到解析器报错,八成是扩展部分出问题了。检查一下 value 里有没有未转义的空格或竖线,这是最常见的故障点。
好了,CEF 格式就讲到这里。下一块我们要聊的是另一个常见格式 LEEF——它跟 CEF 很像但细节又不一样。不过那是后面的事了,先把基础打牢再说。