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 解码器的结构:name、prematch、regex、order
我们从零开始构建一个解码器,并逐一解释每个标签。假设你有一个自定义应用程序,写出如下格式的日志:
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 登录失败 | ✅ 需要——需要 user 和 srcip 字段 |
| 源 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 的流水线就变得非常简单:
- 解码器先运行——使用
<prematch>(过滤)、<regex>(提取)和<order>(命名)将原始日志文本转换为命名字段。只有<regex>的捕获组才能成为字段,<prematch>不提取任何内容。 - 规则后运行——
<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
Related Posts
- Wazuh Decoders & Rules: The Missing Mental Model
- 为制造工厂构建实时OEE追踪系统
- Building a Real-Time OEE Tracking System for Manufacturing Plants
- The $1M Enterprise Software Myth: How Open‑Source + AI Are Replacing Expensive Corporate Platforms
- 电商数据缓存实战:如何避免展示过期价格与库存
- How to Cache Ecommerce Data Without Serving Stale Prices or Stock
- AI驱动的遗留系统现代化:将机器智能集成到ERP、SCADA和本地化部署系统中
- AI-Driven Legacy Modernization: Integrating Machine Intelligence into ERP, SCADA, and On-Premise Systems
- The Price of Intelligence: What AI Really Costs
- 为什么你的 RAG 应用在生产环境中会失败(以及如何修复)
- Why Your RAG App Fails in Production (And How to Fix It)
- AI 时代的 AI-Assisted Programming:从《The Elements of Style》看如何写出更高质量的代码
- AI-Assisted Programming in the Age of AI: What *The Elements of Style* Teaches About Writing Better Code with Copilots
- AI取代人类的迷思:为什么2026年的企业仍然需要工程师与真正的软件系统
- The AI Replacement Myth: Why Enterprises Still Need Human Engineers and Real Software in 2026
- NSM vs AV vs IPS vs IDS vs EDR:你的企业安全体系还缺少什么?
- NSM vs AV vs IPS vs IDS vs EDR: What Your Security Architecture Is Probably Missing
- AI驱动的 Network Security Monitoring(NSM)
- AI-Powered Network Security Monitoring (NSM)
- 使用开源 + AI 构建企业级系统













