Wazuh 解码器与规则:缺失的思维模型

一份清晰的入门指南,详解 Wazuh 解码器与规则如何协同工作——字段是什么、从哪里来、何时需要解码器,以及日志如何变成告警。

标签: Wazuh · OSSEC · SIEM · 蓝队 · 检测工程
难度: 入门 → 中级 | 阅读时间: 15 分钟


如果你曾打开 Wazuh 规则文件,然后问自己:

  • "这个字段从哪里来——规则、解码器,还是日志本身?"
  • "这条规则要生效,我真的需要解码器吗?"
  • "明明日志里有这个字符串,为什么 <field> 规则就是不触发?"

……你不是一个人。这些正是大多数人刚开始写 Wazuh 规则时必然会遇到的问题。本文将逐步解答所有疑惑,并在每个阶段配以流程图。


1. 全局视角:Wazuh 如何处理每一条日志

在任何规则被评估之前,Wazuh 会将每一条传入的日志通过一条两阶段流水线:

flowchart LR
    A(["📄 Raw Log"]) --> B["Decoder Stage\nextract named fields"]
    B --> C["Rules Engine\nmatch conditions"]
    C --> D(["🚨 Alert or No Alert"])

    style A fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style B fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style C fill:#2a1f10,stroke:#ff6b35,color:#c8cdd8
    style D fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0

第一阶段 — 解码器(Decoder): 接收原始日志字符串,从中提取结构化的命名字段。此阶段是可选的——如果没有解码器匹配该日志,日志将作为纯字符串直接通过。

第二阶段 — 规则引擎(Rules Engine): 将日志(以及已解码的字段)与你的规则进行比对。此阶段始终运行,无论有没有解码器。

核心洞察:规则引擎能"看到"什么,完全取决于解码器是否事先运行过。


2. Wazuh 中的"字段"是什么?

字段是解码器从原始日志中提取出来的命名数据片段。

以这条原始 SSH 日志为例:

Jan 10 12:00:00 webserver sshd[1234]: Failed password for root from 203.0.113.5 port 22

Wazuh 内置的 SSH 解码器读取该行后,会提取出:

srcip  = 203.0.113.5
user   = root
action = Failed password

这些就成为了命名字段,规则可以对它们进行精确匹配,而无需扫描整个原始字符串。

没有解码器时,规则引擎只能看到原始日志文本。有了解码器,它既能看到原始文本,能看到提取出的字段。


3. 规则与解码器如何关联

这是大多数人感到困惑的地方。规则标签有好几种,它们的行为会因解码器是否运行而大相径庭:

规则标签 需要解码器? 匹配对象
<match> ❌ 不需要 对原始日志行进行子字符串搜索
<field name="x"> ✅ 需要 解码器提取出的命名字段的值
<decoded_as> ✅ 需要 特定解码器是否处理了此日志
<if_sid> ❌ 不需要 父规则是否之前已触发(链式规则)
<same_field> ✅ 需要 跨多个事件的字段关联
<list field="x"> ✅ 需要 对已解码字段进行 CDB / 威胁情报查询
flowchart TD
    A(["Raw Log"]) --> B{"Did a decoder\nrun on this log?"}

    B -- "No" --> C["Only raw string\navailable to rules"]
    B -- "Yes" --> D["Named fields\navailable to rules"]

    C --> E["match ✅\nif_sid ✅\nfield ❌\ndecoded_as ❌"]
    D --> F["match ✅\nif_sid ✅\nfield ✅\ndecoded_as ✅"]

    style A fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style B fill:#2a1f10,stroke:#ff6b35,color:#ff6b35
    style C fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style D fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style E fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style F fill:#0d2e1a,stroke:#00e5a0,color:#c8cdd8

<match> 始终有效,因为它只是对原始日志文本做子字符串搜索,不关心解码器是否运行过。<field> 只有在解码器已按名称提取了该字段的情况下才有效。


4. Wazuh 解码器的结构:nameprematchregexorder

我们从零开始构建一个解码器,并逐一解释每个标签。假设你有一个自定义应用程序,写出如下格式的日志:

2024-01-10 12:00:00 app=myapp action=login user=alice srcip=203.0.113.5 status=failed

下面是一个提取有用字段的解码器:

<decoder name="myapp">
  <prematch>app=myapp</prematch>
  <regex>action=(\w+) user=(\w+) srcip=(\d+\.\d+\.\d+\.\d+) status=(\w+)</regex>
  <order>action, user, srcip, status</order>
</decoder>
flowchart LR
    A(["Raw Log\napp=myapp action=login user=alice ..."])
    A --> B{"prematch\napp=myapp found?"}
    B -- "No" --> C(["Skip this decoder"])
    B -- "Yes" --> D["Run regex\nextract capture groups"]
    D --> E["Map via order\ngroup 1 to action\ngroup 2 to user\ngroup 3 to srcip\ngroup 4 to status"]
    E --> F(["Fields ready\naction=login\nuser=alice\nsrcip=203.0.113.5\nstatus=failed"])

    style A fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style B fill:#2a1f10,stroke:#ff6b35,color:#ff6b35
    style C fill:#2e0d0d,stroke:#ff4444,color:#888
    style D fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style E fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style F fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0

name="myapp"

该解码器的名称标签。规则可以通过 <decoded_as>myapp</decoded_as> 引用它,从而确保只对经过该解码器处理的日志触发。

<prematch> — 快速预过滤器

在运行正则表达式之前,Wazuh 先检查:这个字符串在日志中存在吗? 如果不存在,整个解码器会被跳过。这是一种性能优化——正则匹配代价高昂,而 prematch 开销极低。务必加上它。

Log: "2024-01-10 app=myapp action=login user=alice ..."
                  ^^^^^^^^
              prematch 找到 "app=myapp" → 继续执行 regex

Log: "Jan 10 kernel: eth0 link up"
              未找到 "app=myapp" → 完全跳过此解码器

<regex> — 捕获组提取值

每一对 () 都是一个捕获组。正则在括号内匹配到的内容就成为字段的值。

action=(\w+)
        └──┘  捕获组 1 — 匹配一个或多个单词字符
              输入:  action=login
              结果:  login  → 捕获组 1

user=(\w+)
      └──┘  捕获组 2
              输入:  user=alice
              结果:  alice  → 捕获组 2

srcip=(\d+\.\d+\.\d+\.\d+)
       └─────────────────┘  捕获组 3 — IP 地址模式
              输入:  srcip=203.0.113.5
              结果:  203.0.113.5  → 捕获组 3

status=(\w+)
        └──┘  捕获组 4
              输入:  status=failed
              结果:  failed  → 捕获组 4

<order> — 为捕获组命名

<order>action, user, srcip, status</order>

这将建立以下映射:捕获组 1 → action,捕获组 2 → user,捕获组 3 → srcip,捕获组 4 → status

⚠️ 关键规则: <order> 中的名称数量必须与 <regex>() 捕获组的数量完全一致。4 个捕获组 → 4 个名称。若不匹配,解码器将静默失败,不会提取任何字段,也不会给出任何报错。


5. 常见误区:<prematch> 不会提取字段

这是 Wazuh 新手中最常见的误解。

问:我的 <prematch> 检查了 app=myapp——那么 app 会成为规则中可用的字段吗?

不会。 <prematch> 只是一个过滤器。它检查字符串是否存在于日志中,以决定是否继续处理。它不提取任何内容。

flowchart TD
    A["prematch: app=myapp"] --> B["Filter only ❌\napp NOT a field\ncannot use in rules"]
    C["regex: app=group1 user=group2\norder: app, user"] --> D["app = myapp ✅\nuser = alice ✅\nboth usable in rules"]

    style A fill:#2a1f10,stroke:#ff6b35,color:#c8cdd8
    style B fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style C fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style D fill:#0d2e1a,stroke:#00e5a0,color:#c8cdd8

字段只能来自 <regex> 的捕获组,并通过 <order> 进行映射。如果你想让 app 成为可用字段,必须在正则中捕获它:

<!-- app 不是字段 —— 仅用作预过滤器 -->
<prematch>app=myapp</prematch>
<regex>action=(\w+) user=(\w+)</regex>
<order>action, user</order>

<!-- app 是字段 —— 已在 regex 中捕获 -->
<prematch>app=</prematch>
<regex>app=(\w+) action=(\w+) user=(\w+)</regex>
<order>app, action, user</order>

6. Wazuh 完整流程:原始日志 → 解码器 → 规则链 → 告警

我们来端到端追踪一个真实示例。一个用户连续登录失败三次——我们希望用链式规则检测这种行为。

原始日志:

2024-01-10 12:00:00 app=myapp action=login user=alice srcip=203.0.113.5 status=failed
flowchart TD
    A(["Raw Log - app=myapp action=login user=alice status=failed"])
    A --> B{"prematch - app=myapp found?"}

    B -- "No" --> C(["No decoder runs - log stays as raw string"])
    B -- "Yes" --> D["Regex extracts: action=login, user=alice, srcip=203.0.113.5, status=failed"]

    C --> F
    D --> F

    F(["Rules Engine"])
    F --> G

    G{"Rule 1000 - match: app=myapp"}
    G -- "string not found" --> Z(["No Alert"])
    G -- "string found" --> H

    H{"Rule 1001 - if_sid: 1000 - field: status=failed"}
    H -- "field missing - no decoder ran" --> Z
    H -- "field exists" --> I

    I{"Rule 1002 - if_sid: 1001 - same_field: user - frequency 3 in 60s"}
    I -- "not yet 3 times" --> Z
    I -- "3 failures same user" --> J

    J(["ALERT level 10 - Brute force: alice failed 3 times"])

    style A fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style B fill:#2a1f10,stroke:#ff6b35,color:#ff6b35
    style C fill:#1a1f2e,stroke:#444,color:#888
    style D fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style F fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style G fill:#2a1f10,stroke:#ff6b35,color:#c8cdd8
    style H fill:#2a1f10,stroke:#ff6b35,color:#c8cdd8
    style I fill:#2a1f10,stroke:#ff6b35,color:#c8cdd8
    style J fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0
    style Z fill:#2e0d0d,stroke:#ff4444,color:#ff4444

注意 Rule 1001 处的分支:如果解码器没有运行,status 字段就从未被提取过。<field name="status"> 的检查会静默失败——即便原始日志中明确包含 status=failed。这是规则"看起来正确却始终不触发"的最常见原因。


7. 没有解码器时 Wazuh 会发生什么?

你的规则仍然可以部分工作——但仅限于那些不需要已提取字段的标签。

规则标签 无解码器时 原因
<match>app=myapp</match> ✅ 正常工作 原始子字符串搜索,不需要字段
<if_sid>1000</if_sid> ✅ 正常工作 规则链,不需要字段
<field name="status">failed</field> ❌ 静默失败 字段 status 从未被提取
<same_field>user</same_field> ❌ 静默失败 字段 user 从未被提取
<list field="srcip">blocklist</list> ❌ 静默失败 字段 srcip 从未被提取
flowchart LR
    A(["No Decoder"]) --> B["match\nraw string search"]
    A --> C["if_sid\nrule chaining"]
    A --> D["field\nextracted field"]
    A --> E["same_field\nfield correlation"]

    B --> F(["✅ Always works"])
    C --> G(["✅ Always works"])
    D --> H(["❌ Always fails"])
    E --> I(["❌ Always fails"])

    style F fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0
    style G fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0
    style H fill:#2e0d0d,stroke:#ff4444,color:#ff4444
    style I fill:#2e0d0d,stroke:#ff4444,color:#ff4444

⚠️ 当 <field> 因无解码器而失败时,Wazuh 不会给出任何警告。它只是不匹配而已。这就是为什么一条看起来正确的规则可能永远静默地不触发。


8. 何时真正需要 Wazuh 解码器?

每次编写新规则时,都可以使用这棵决策树:

flowchart TD
    Q(["What does your rule need to do?"])
    Q --> A["Detect that a\nstring exists in the log"]
    Q --> B["Match a specific\nfield value precisely"]
    Q --> C["Correlate events\nacross multiple logs"]
    Q --> D["Look up a value\nin a threat-intel list"]
    Q --> E["Parse a structured format\nFortiGate or JSON or CEF"]

    A --> A1(["❌ No decoder needed\nuse match"])
    B --> B1(["✅ Decoder needed\nuse field"])
    C --> C1(["✅ Decoder needed\nuse same_field\nor different_field"])
    D --> D1(["✅ Decoder needed\nuse list field"])
    E --> E1(["✅ Decoder needed\nuse decoded_as"])

    style A1 fill:#0d2e1a,stroke:#00e5a0,color:#c8cdd8
    style B1 fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style C1 fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style D1 fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8
    style E1 fill:#2e0d0d,stroke:#ff4444,color:#c8cdd8

实际示例:

想要检测的内容 需要解码器?
日志行中包含单词 error ❌ 不需要——使用 <match>error</match>
来自特定 IP 段的 SSH 登录失败 ✅ 需要——需要 srcip 字段
同一用户名从两个不同 IP 登录失败 ✅ 需要——需要 usersrcip 字段
源 IP 与已知恶意 IP 列表匹配 ✅ 需要——需要 srcip 字段用于 <list>
FortiGate 防火墙拦截了某个连接 ✅ 需要——需要 FortiGate 日志格式的解码器
Windows EventID 4625(登录失败)触发 ❌ 不需要——Wazuh 内置解码器已处理

9. 解码器优先级:内置解码器何时优先运行

Wazuh 内置了针对常见日志源的解码器——SSH、Windows、FortiGate、Apache 等等。这些内置解码器总是在你的自定义解码器之前运行。

当你为 Wazuh 已知的日志源编写自定义规则时,这一点至关重要:

flowchart TD
    A(["Log from a known source\ne.g. FortiGate with date= logid="])
    A --> B{"Does a built-in\ndecoder match?"}

    B -- "Yes" --> C["Built-in decoder fires\ne.g. fortigate-firewall-v6"]
    B -- "No" --> D["Custom decoder checked"]

    C --> E{"Does your custom rule\nchain off the right parent?"}
    E -- "chains off built-in group\nif_group: fortigate" --> F(["✅ Rule fires correctly"])
    E -- "chains off custom rule\nif_sid: your-base-rule" --> G(["❌ Parent never fired\nrule never matches"])

    D --> H(["Custom decoder fires\nnormal flow"])

    style A fill:#1a1f2e,stroke:#5b8fff,color:#c8cdd8
    style B fill:#2a1f10,stroke:#ff6b35,color:#ff6b35
    style C fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8
    style F fill:#0d2e1a,stroke:#00e5a0,color:#00e5a0
    style G fill:#2e0d0d,stroke:#ff4444,color:#ff4444
    style H fill:#1a2e1f,stroke:#00e5a0,color:#c8cdd8

解决方法是使用 <if_group> 从内置解码器的规则组进行链接,而不是从你自己的基础规则:

<!-- ❌ 错误 —— 对 FortiGate 日志,你的基础规则永远不会触发 -->
<rule id="200">
  <if_sid>100</if_sid>   <!-- 你的自定义基础规则 -->
  <field name="action">deny</field>
</rule>

<!-- ✅ 正确 —— 从内置 fortigate 组进行链接 -->
<rule id="200">
  <if_group>fortigate</if_group>
  <field name="action">deny</field>
</rule>

10. 速查表:解码器与规则标签全览

标签 阶段 提取字段? 需要解码器? 用途
<prematch> 解码器 ❌ 否 regex 前的快速预过滤
<regex> + <order> 解码器 ✅ 是 从日志中提取命名字段
<match> 规则 ❌ 不需要 原始子字符串匹配
<field> 规则 ✅ 需要 匹配特定的已提取字段值
<decoded_as> 规则 ✅ 需要 仅在特定解码器运行时匹配
<if_sid> 规则 ❌ 不需要 按 ID 链接到父规则
<if_group> 规则 ❌ 不需要 链接到规则组(如内置 fortigate)
<same_field> 规则 ✅ 需要 关联同一字段值的重复事件
<different_field> 规则 ✅ 需要 检测同一用户、不同 IP(异常地理位置)
<list field> 规则 ✅ 需要 对已解码字段进行威胁情报 CDB 查询

总结

一旦内化了两阶段模型,Wazuh 的流水线就变得非常简单:

  1. 解码器先运行——使用 <prematch>(过滤)、<regex>(提取)和 <order>(命名)将原始日志文本转换为命名字段。只有 <regex> 的捕获组才能成为字段,<prematch> 不提取任何内容。
  2. 规则后运行——<match> 无需解码器,直接作用于原始文本;<field><same_field><list> 只有在解码器已提取相应字段后才能工作。

当规则静默地始终不触发时,原因几乎总是以下之一:

  • 使用了 <field>,但没有解码器提取过该字段
  • 内置解码器比你的自定义解码器先触发,导致 <if_sid> 链断裂
  • <order> 中的名称数量与 <regex> 的捕获组数量不匹配

从只使用 <match> 的规则开始,确认日志正在到达 Wazuh 且基础规则正在触发。在需要精确的字段级匹配时,再添加解码器和 <field> 规则。


常见问题解答

问:<prematch> 会创建规则中可用的命名字段吗?
不会。<prematch> 只是一个性能过滤器——它在运行 regex 之前检查字符串是否存在于日志中,不提取任何内容。字段只由通过 <order> 映射的 <regex> 捕获组生成。

问:没有解码器,Wazuh 规则也能触发吗?
可以,只要规则仅使用 <match><if_sid>。这两者作用于原始日志字符串,不需要已解码的字段。而 <field><decoded_as><same_field><list field> 等标签在没有解码器时会静默失败。

问:Wazuh 中 <if_sid> 的用途是什么?
<if_sid> 通过规则 ID 将子规则链接到父规则。子规则只有在父规则已对同一事件匹配成功时才会触发。这正是 Wazuh 构建级联检测的方式——基础规则匹配日志来源,子规则匹配其中的具体条件。

问:Wazuh 规则中 <match><field> 的区别是什么?
<match> 对整个原始日志行进行子字符串搜索,不需要解码器。<field name="x"> 则针对解码器必须已提取的特定命名字段进行匹配。宽泛检测用 <match>,需要精确匹配特定值时用 <field>

问:Wazuh 解码器的捕获组数量有上限吗?
文档中没有明确的上限记载,但 <regex>() 捕获组的数量必须与 <order> 中逗号分隔的名称数量完全一致。不匹配时,解码器会静默失败,不会输出任何错误信息。

问:日志中明明有这个字符串,规则为什么不触发?
最可能的原因:(1) 使用了 <field>,但没有解码器提取过该字段;(2) 内置解码器先触发,导致 <if_sid> 链断裂;(3) <prematch> 中的字符串在日志中不存在。使用 wazuh-logtest 可以追踪任意日志行的完整解码器和规则路径。

问:wazuh-logtest 是什么?怎么使用?
wazuh-logtest 是 Wazuh 内置工具,允许你粘贴一行原始日志,精确查看哪个解码器匹配了、哪些字段被提取了、哪些规则触发了。这是调试行为异常的解码器或规则的最快方式。可以从 Wazuh 管理器 CLI 运行,也可以在 Wazuh Web UI 的 Tools → Logtest 中使用。


检测工程系列文章之一。如发现错误或希望补充示例,欢迎提 Issue 或联系我们。


Get in Touch with us

Chat with Us on LINE

iiitum1984

Speak to Us or Whatsapp

(+66) 83001 0222

Related Posts

Our Products