3、日志标准化:日志格式规范(CEF、LEEF、JSON)、字段映射与清洗、时间戳归一化处理

好,咱们进入第三个话题。日志标准化。

说实话,我见过太多安全团队,日志收了一堆,结果告警全是垃圾。为什么?因为日志格式不统一。你想想看,防火墙用 Syslog,Windows 用 EventLog,Web 服务器用 JSON,数据库又用 CSV……这些日志堆在一起,就像把中文、英文、阿拉伯文混在一起写报告,根本没法看。

所以,标准化是日志分析的第一步,也是最重要的一步。这一步做不好,后面所有分析都是空中楼阁。

3.1 日志格式规范:CEF、LEEF、JSON

目前业界主流的日志格式有三种:CEF、LEEF 和 JSON。我一个个说。

3.1.1 CEF(Common Event Format)

CEF 是 ArcSight 搞出来的格式。说白了,它就是一种键值对格式,但结构固定。长这样:

CEF:0|Security|ThreatManager|1.0|100|Detected malware|10|src=10.0.0.1 dst=10.0.0.2 msg=Malware found

看到没?竖线分隔的是固定字段,后面是扩展字段。固定字段包括设备厂商、设备产品、版本、事件ID、事件名称、严重级别。扩展字段就是自定义的键值对。

我个人习惯用 CEF 来处理老牌安全设备,比如防火墙、IDS/IPS。因为这些设备厂商基本都支持 CEF,兼容性最好。

小技巧: 如果你用 Splunk 或 QRadar,CEF 解析器是内置的,直接就能用。但如果你用 ELK,需要自己写 Grok 正则来解析。

3.1.2 LEEF(Log Event Extended Format)

LEEF 是 IBM 的 QRadar 推的格式。它跟 CEF 很像,但结构稍微不同:

LEEF:1.0|IBM|QRadar|7.0|100|devTime=2024-01-15T10:30:00Z src=10.0.0.1 dst=10.0.0.2

LEEF 的固定字段更少,主要靠扩展字段。它的优势在于时间戳处理更灵活,支持多种时间格式。

我记得有一次做项目,客户用的是 QRadar,但日志源是自家开发的系统。我建议他们直接输出 LEEF 格式,省去了大量解析工作。嗯,这就是选对格式的好处。

3.1.3 JSON

JSON 是目前最流行的格式。为什么?因为灵活、可读性强、解析方便。几乎所有现代应用都支持 JSON。

{
  "event_id": "100",
  "event_name": "Detected malware",
  "severity": 10,
  "src_ip": "10.0.0.1",
  "dst_ip": "10.0.0.2",
  "timestamp": "2024-01-15T10:30:00Z"
}

JSON 的好处是嵌套结构。你可以把关联信息放在一起,比如把用户信息和操作信息放在同一个对象里。这在做关联分析时特别方便。

注意: JSON 虽然好,但日志量大的时候,解析开销也大。我曾经遇到一个客户,每秒产生 10 万条 JSON 日志,结果 Logstash 直接撑爆了。后来我们改成用 Fluentd 做轻量级解析,才扛住。

3.2 字段映射与清洗

日志格式统一了,接下来就是字段映射。说白了,就是把不同来源的日志字段,映射到统一的字段名上。

举个例子:

  • 防火墙的源IP字段叫 src_ip
  • Windows 事件日志的源IP字段叫 IpAddress
  • Web 服务器的源IP字段叫 remote_addr

如果不做映射,你写查询的时候就得记一堆字段名,累不累?

我建议的做法是:

  1. 定义统一字段模型:比如所有源IP都叫 src_ip,所有目标IP都叫 dst_ip,所有时间戳都叫 timestamp。
  2. 写解析规则:用 Grok、正则或者 Logstash filter 把原始字段映射到统一字段。
  3. 清洗脏数据:比如去掉空值、修正格式错误、过滤掉无意义的数据。

这里我分享一个我常用的 Logstash 配置片段:

filter {
  if [type] == "firewall" {
    mutate {
      rename => { "source_ip" => "src_ip" }
      rename => { "destination_ip" => "dst_ip" }
    }
  }
  if [type] == "windows" {
    mutate {
      rename => { "IpAddress" => "src_ip" }
    }
  }
  # 清洗:去掉空值
  if [src_ip] == "" or [src_ip] == "-" {
    drop {}
  }
}

你看,逻辑很简单。但实际项目中,字段映射往往是最耗时的环节。我曾经花了两周时间,就为了把 30 多种日志源的字段统一起来。嗯,那段时间真是噩梦。

核心原则: 字段映射要「宁多勿少」。宁可多映射一些用不到的字段,也不要漏掉关键字段。因为一旦漏了,后面想补就难了。

3.3 时间戳归一化处理

时间戳,这是日志标准化里最坑的地方。没有之一。

你想想看:

  • 防火墙用 Jan 15 10:30:00
  • Windows 用 2024-01-15T10:30:00.123Z
  • Web 服务器用 1705300200(Unix 时间戳)
  • 数据库用 2024-01-15 10:30:00

这些时间格式混在一起,你怎么做时间排序?怎么做关联分析?

所以,时间戳归一化是必须的。我推荐的做法是:

  1. 统一转为 ISO 8601 格式:比如 2024-01-15T10:30:00Z。这个格式可读性好,而且支持时区。
  2. 保留原始时间戳:不要直接覆盖原始字段,而是新建一个 @timestamp 字段。
  3. 处理时区问题:所有时间统一转为 UTC,避免时区混乱。

这里我给出一个 Logstash 的时间戳处理示例:

filter {
  date {
    match => ["original_timestamp", "ISO8601", "UNIX", "MMM dd HH:mm:ss"]
    target => "@timestamp"
    timezone => "UTC"
  }
}

你看,用 date 插件,指定多种匹配格式,Logstash 会自动尝试解析。解析成功后,会把结果写入 @timestamp 字段。

避坑指南: 我曾经遇到过一个案例,日志里的时间戳是 2024-01-15 10:30:00,但没带时区。结果 Logstash 默认当成 UTC,而实际是东八区。所有日志都差了 8 小时。后来我加了一行 timezone => "Asia/Shanghai" 才解决。所以,时间戳一定要明确时区,否则后患无穷。

另外,还有一个常见问题:日志延迟。比如事件发生在 10:00,但日志在 10:05 才到达。这时候,@timestamp 应该用事件时间,而不是接收时间。否则你分析的时候会发现,10:05 的日志里混着 10:00 的事件,乱套了。

嗯,时间戳归一化,说白了就是「统一格式、统一时区、统一字段」。这三统一做到了,后面的分析才能靠谱。

小结

日志标准化,听起来简单,做起来全是坑。但只要你把格式规范、字段映射、时间戳归一化这三件事做好,后面的异常行为挖掘就会顺畅很多。

我个人经验是:标准化工作至少占整个日志分析项目的 40% 时间。别嫌多,这一步省了,后面十倍百倍地还回来。

好,这一节就到这里。下一节我们聊聊日志采集架构,看看怎么把标准化后的日志高效地收上来。