Wazuh Decoders & Rules: 欠けていたメンタルモデル
Wazuh のデコーダーとルールがどのように連携するかを初心者にも分かりやすく解説。フィールドとは何か、どこから来るのか、デコーダーが必要なタイミング、そしてログがアラートになるまでの流れを説明します。
タグ: Wazuh · OSSEC · SIEM · Blue Team · Detection Engineering
レベル: 初級 → 中級 | 読了時間: 15 分
Wazuh のルールファイルを見て、こんな疑問を持ったことはありませんか?
- 「このフィールドはどこから来ているのか — ルール?デコーダー?それともログ自体?」
- 「このルールを動かすためにデコーダーは必要なのか?」
- 「ログに文字列が明らかに存在するのに、なぜ
<field>ルールが発火しないのか?」
……あなただけではありません。これらは Wazuh ルールを書き始めた人が必ずぶつかる疑問です。この記事ではそのすべてに、図を交えながら順を追って答えます。
1. 全体像: Wazuh はすべてのログをどう処理するか
ルールが評価される前に、Wazuh はすべての受信ログを 2 段階のパイプラインに通します。
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
第 1 段階 — Decoder: raw ログ文字列を受け取り、構造化された名前付きフィールドを抽出します。この段階はオプションです。ログにマッチするデコーダーがなければ、ログはそのままプレーン文字列として通過します。
第 2 段階 — Rules Engine: ルールに対してログ(およびデコードされたフィールド)を評価します。この段階はデコーダーの有無にかかわらず、常に実行されます。
重要なポイント: ルールエンジンが何を「見る」かは、デコーダーが先に実行されたかどうかによって完全に決まります。
2. Wazuh における「フィールド」とは何か
フィールドとは、デコーダーが raw ログから抽出した名前付きのデータです。
次の raw 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
これらが 名前付きフィールド となり、ルールは raw 文字列を走査するのではなく、フィールドに対して正確にマッチングできるようになります。
デコーダーがない場合、ルールエンジンは raw ログテキストしか見えません。デコーダーがある場合は、raw テキストと抽出されたフィールドの両方が見えます。
3. ルールとデコーダーの接続関係
ここが多くの人が混乱するポイントです。ルールタグには複数の種類があり、デコーダーが実行されたかどうかによって動作が大きく異なります。
| Rule Tag | デコーダー必要? | マッチ対象 |
|---|---|---|
<match> |
❌ 不要 | raw ログ行の部分文字列検索 |
<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> は raw ログテキストへの単純な部分文字列検索であるため、デコーダーの有無を問わず常に機能します。<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> — 高速プリフィルター
regex を実行する前に、Wazuh はまず「この文字列がログ内に存在するか?」を確認します。存在しなければ、デコーダー全体がスキップされます。これはパフォーマンス最適化です — regex は処理コストが高く、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> — キャプチャグループによる値の抽出
() の各ペアが キャプチャグループ です。括弧内で regex がマッチした文字列がフィールド値になります。
action=(\w+)
└──┘ グループ 1 — 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 を使い始めた人に最も多い誤解です。
Q: <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 を使用可能なフィールドにしたい場合は、regex でキャプチャする必要があります。
<!-- 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 の全処理フロー: Raw Log → Decoder → Rule Chain → Alert
現実的な例をエンドツーエンドで追います。ユーザーが 3 回ログインに失敗した場合、これをチェーンルールで検知します。
Raw log:
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 フィールドは一度も抽出されていません。raw ログに status=failed が明確に含まれていても、<field name="status"> のチェックはサイレントに失敗します。これがルールが動かない最も一般的な原因です。
7. デコーダーなしでは何が起きるか
ルールは部分的には機能します — ただし、抽出済みフィールドを必要としないタグのみです。
| Rule Tag | デコーダーなしの場合 | 理由 |
|---|---|---|
<match>app=myapp</match> |
✅ 動作する | raw 文字列検索 — フィールド不要 |
<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
⚠️ Wazuh は
<field>がデコーダーなしで失敗しても警告を出しません。単にマッチしないだけです。正しく見えるルールがサイレントに発火しない原因はここにあります。
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 フィールドが必要 |
| 同じユーザー名が 2 つの異なる IP からログイン失敗 | ✅ 必要 — user と srcip フィールドが必要 |
| ソース IP が既知の悪意ある IP リストにマッチ | ✅ 必要 — <list> のために srcip フィールドが必要 |
| 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. クイックリファレンス: Decoder & Rule タグ早見表
| Tag | 段階 | フィールド抽出 | デコーダー必要? | 用途 |
|---|---|---|---|---|
<prematch> |
Decoder | ❌ なし | — | regex 前の高速プリフィルター |
<regex> + <order> |
Decoder | ✅ あり | — | ログから名前付きフィールドを抽出 |
<match> |
Rule | — | ❌ 不要 | raw 文字列の部分マッチ |
<field> |
Rule | — | ✅ 必要 | 抽出済みフィールドの値をマッチ |
<decoded_as> |
Rule | — | ✅ 必要 | 特定のデコーダーが実行された場合のみマッチ |
<if_sid> |
Rule | — | ❌ 不要 | ID でルールを親ルールにチェーン |
<if_group> |
Rule | — | ❌ 不要 | ルールグループにチェーン(例: 組み込み fortigate) |
<same_field> |
Rule | — | ✅ 必要 | 同一フィールド値での繰り返しイベントを相関 |
<different_field> |
Rule | — | ✅ 必要 | 同一ユーザー・異なる IP を検知(不審な移動) |
<list field> |
Rule | — | ✅ 必要 | デコードされたフィールドの脅威インテル CDB 照合 |
まとめ
2 段階モデルを理解すれば、Wazuh のパイプラインはシンプルです。
- Decoders が先に実行される —
<prematch>(フィルター)、<regex>(抽出)、<order>(名前付け)を使って raw ログテキストを名前付きフィールドに変換します。フィールドになるのは<regex>のキャプチャグループのみです。<prematch>は何も抽出しません。 - Rules が後に実行される —
<match>はデコーダー不要で raw テキストに対して動作します。<field>、<same_field>、<list>は、デコーダーがそれらのフィールドを抽出済みの場合にのみ動作します。
ルールがサイレントに発火しない場合、原因はほぼ必ずこのいずれかです。
<field>を使ったが、デコーダーがそのフィールドを抽出していない- 組み込みデコーダーがカスタムより先に発火し、
<if_sid>チェーンが壊れた <order>の名前数が<regex>のグループ数と一致していない
まず <match> のみを使ったルールで、ログが Wazuh に届いていること・ベースルールが発火していることを確認してください。正確なフィールドレベルのマッチングが必要になったら、その後でデコーダーと <field> ルールを追加します。
よくある質問(FAQ)
Q: <prematch> はルールで使える名前付きフィールドを作りますか?
いいえ。<prematch> はパフォーマンス用フィルターにすぎません。regex を実行する前に文字列がログ内に存在するかを確認するだけで、何も抽出しません。フィールドは <order> でマッピングされた <regex> のキャプチャグループのみが生成します。
Q: デコーダーなしでも Wazuh のルールは発火しますか?
はい、<match> または <if_sid> のみを使っている場合に限ります。これらは raw ログ文字列に対して動作し、デコードされたフィールドを必要としません。<field>、<decoded_as>、<same_field>、<list field> などのタグはデコーダーなしでサイレントに失敗します。
Q: Wazuh における <if_sid> の用途は何ですか?
<if_sid> は、ルール ID を使って子ルールを親ルールにチェーンします。子ルールは、同一イベントで親ルールがすでにマッチした場合にのみ発火します。これが Wazuh のカスケード検知の仕組みです — ベースルールがログソースをマッチし、子ルールがその中の具体的な条件をマッチします。
Q: Wazuh のルールにおける <match> と <field> の違いは何ですか?
<match> は raw ログ行全体に対して部分文字列検索を行います — デコーダー不要。<field name="x"> は、デコーダーがすでに抽出した特定の名前付きフィールドに対してマッチします。広範な検知には <match> を、特定の値に対して精度が必要な場合は <field> を使ってください。
Q: Wazuh デコーダーのキャプチャグループ数に上限はありますか?
ドキュメントには明確な上限の記載はありませんが、<regex> の () グループ数は <order> のカンマ区切りの名前数と完全に一致しなければなりません。不一致があると、デコーダーはエラーメッセージなしにサイレント失敗します。
Q: ログに文字列が含まれているのにルールが発火しないのはなぜですか?
最も考えられる原因: (1) <field> を使ったが、デコーダーがそのフィールドを抽出していない、(2) 組み込みデコーダーが先に発火し、<if_sid> チェーンが壊れた、(3) <prematch> の文字列がログに存在しない。wazuh-logtest を使って、任意のログ行のデコーダーとルールの正確なパスを追跡してください。
Q: wazuh-logtest とは何ですか?どのように使いますか?
wazuh-logtest は Wazuh に組み込まれたツールで、raw ログ行を貼り付けるだけで、どのデコーダーがマッチしたか、どのフィールドが抽出されたか、どのルールが発火したかを正確に確認できます。デコーダーやルールが期待通りに動作しない場合のデバッグに最も速い方法です。Wazuh マネージャーの CLI から、または Wazuh Web UI の Tools → Logtest から実行できます。
Detection Engineering シリーズの一部です。誤りを見つけた場合や例の追加提案がある場合は、Issue を開くかご連絡ください。
Get in Touch with us
Related Posts
- 製造現場向けリアルタイムOEE管理システムの構築
- 古い価格や在庫を表示しないECサイトのキャッシュ戦略
- AIによるレガシーシステム modernization:ERP・SCADA・オンプレミス環境へのAI/ML統合ガイド
- RAGアプリが本番環境で失敗する理由(そして解決策)
- AI時代のAI-Assisted Programming:『The Elements of Style』から学ぶ、より良いコードの書き方
- AIが人間を代替するという幻想:なぜ2026年の企業はエンジニアと本物のソフトウェアを必要とするのか
- NSM vs AV vs IPS vs IDS vs EDR:あなたのセキュリティ対策に不足しているものは何か?
- AI搭載 Network Security Monitoring(NSM)
- オープンソース + AIで構築するエンタープライズシステム
- AIは2026年にソフトウェア開発会社を置き換えるのか?経営層が知るべき本当の話
- オープンソース + AIで構築するエンタープライズシステム(2026年 実践ガイド)
- AI活用型ソフトウェア開発 — コードを書くためではなく、ビジネスのために
- Agentic Commerce:自律型購買システムの未来(2026年完全ガイド)
- 現代 SOC における Automated Decision Logic の構築方法(Shuffle + SOC Integrator 編)
- なぜ私たちは Tool-to-Tool ではなく SOC Integrator を設計したのか
- OCPP 1.6によるEV充電プラットフォーム構築 ダッシュボード・API・実機対応の実践デモガイド
- ソフトウェア開発におけるスキル進化(2026年)
- Retro Tech Revival:クラシックな思想から実装可能なプロダクトアイデアへ
- OffGridOps — 現場のためのオフライン・フィールドオペレーション
- SmartFarm Lite — オフラインで使える、シンプルな農業記録アプリ













