What You'll Learn
- Understand the full log processing pipeline: raw log → decoder → rule → alert
- Read Wazuh decoder XML and explain how it extracts fields from raw logs
- Read Wazuh rule XML and identify trigger conditions, severity, groups, and MITRE mappings
- Explain rule chaining — how simple rules combine to detect complex attack patterns
- Understand rule levels, frequency rules, and composite rules
- Identify when a rule needs tuning and the principles of effective tuning
The Engine Behind the Alerts
In Lessons 2.1–2.4, you learned to read alerts, build dashboards, and search for events. But you've been treating the SIEM as a black box — logs go in, alerts come out. Now it's time to open that box and understand the engine.
Every alert you've investigated in this course was created by two components working together:
- Decoders — parse raw log text into structured fields
- Rules — evaluate those fields against detection logic and generate alerts
Understanding rules and decoders transforms you from a consumer of alerts to someone who can evaluate, tune, and eventually write detection logic. This is the bridge from L1 (triage) to L2 (investigation and detection engineering).
Why This Matters for Your Career: "Can you read a detection rule?" is a common SOC interview question. Understanding rule logic is expected for any analyst role above entry level. In Module 8 (Sigma), you'll write cross-platform detection rules — but first, you need to understand how rules work in one SIEM.
Stage 1: Decoders — Parsing Raw Logs
A raw log is just a line of text. Before any detection can happen, Wazuh needs to understand that text — extract the fields, identify the source, and structure the data for rule evaluation.
What a Raw Log Looks Like
Here's a raw SSH failure log from /var/log/auth.log:
Jan 15 06:20:18 linux-web-01 sshd[5102]: Failed password for root from 185.220.101.42 port 44921 ssh2
This is just a string. Wazuh needs to extract: the timestamp, hostname, program (sshd), the event type (failed password), the target user (root), the source IP (185.220.101.42), and the port (44921).
How Decoders Work
Decoders use regular expressions (regex) and field extraction patterns to parse raw logs. They work in a chain:
- Pre-decoder — extracts the standard syslog header (timestamp, hostname, program)
- Parent decoder — identifies the log source (e.g., "this is an sshd log")
- Child decoder — extracts event-specific fields (source IP, user, port)
Decoder XML Example
Here's a simplified version of Wazuh's SSH decoder:
<!-- Parent decoder: identifies sshd logs -->
<decoder name="sshd">
<program_name>sshd</program_name>
</decoder>
<!-- Child decoder: extracts failed password fields -->
<decoder name="sshd-failed">
<parent>sshd</parent>
<prematch>^Failed password for </prematch>
<regex>^Failed password for (\S+) from (\S+) port (\S+)</regex>
<order>user, srcip, srcport</order>
</decoder>
Reading this decoder:
| Element | Purpose | Value |
|---|---|---|
<parent>sshd</parent> | Only applies to logs already identified as sshd | Chains to the parent decoder |
<prematch> | Quick check — does the log start with "Failed password for"? | Fast filtering before regex |
<regex> | Captures three groups: username, IP, port | \S+ matches any non-whitespace string |
<order> | Names the captured groups | user → srcip → srcport |
After decoding, the raw log becomes structured data:
| Field | Value |
|---|---|
program_name | sshd |
user | root |
srcip | 185.220.101.42 |
srcport | 44921 |
4,000+ Built-in Decoders. Wazuh ships with decoders for hundreds of log formats: sshd, Apache, Windows Event Logs, iptables, named (DNS), sudo, PAM, auditd, and more. You rarely need to write decoders from scratch — but understanding how they work helps you troubleshoot when a rule doesn't fire because the decoder didn't extract the right field.
Stage 2: Rules — Detection Logic
Once the log is decoded into structured fields, the rule engine evaluates those fields against its detection rules. If a rule's conditions match, it generates an alert.
Rule XML Structure
Here's a simplified version of the SSH brute force rule you've encountered throughout this course:
<rule id="5551" level="10" frequency="10" timeframe="120">
<if_matched_sid>5503</if_matched_sid>
<same_source_ip />
<description>sshd: brute force trying to get access to the system.</description>
<mitre>
<id>T1110</id>
</mitre>
<group>authentication_failed,sshd,</group>
</rule>
Reading this rule field by field:
| Element | Purpose | Value |
|---|---|---|
id="5551" | Unique identifier — this is the rule.id you see in alerts | 5551 |
level="10" | Severity on the 0-15 scale — High | 10 |
frequency="10" | Must see this condition 10 times before firing | Threshold-based detection |
timeframe="120" | Within a 120-second (2-minute) window | Time constraint |
<if_matched_sid>5503</if_matched_sid> | Depends on rule 5503 (single SSH failure) having fired | Rule chaining |
<same_source_ip /> | All 10 occurrences must come from the same IP | Correlation constraint |
<description> | Human-readable explanation | What analysts see in the dashboard |
<mitre><id>T1110</id></mitre> | Maps to ATT&CK technique | Brute Force |
<group> | Category tags | Used for filtering and searching |
What This Rule Actually Says
In plain English: "If rule 5503 (single SSH failure) fires 10 times from the same source IP within a 2-minute window, generate a level 10 alert for SSH brute force (T1110)."
This is a frequency rule — it doesn't fire on a single event. It requires a pattern: multiple occurrences of a simpler rule within a time window. This is how Wazuh reduces noise — one failed login is level 5 (low). Ten failed logins from the same IP in 2 minutes is level 10 (high).
Rule Types
Wazuh has several rule types, each designed for different detection patterns:
Atomic Rules (Single Event)
The simplest type — matches a single event based on decoded fields.
<rule id="5503" level="5">
<if_sid>5500</if_sid>
<match>Failed password</match>
<description>sshd: authentication failed.</description>
</rule>
This fires once for every single failed SSH password attempt. Level 5 (low) because one failure is normal — people mistype passwords.
Frequency Rules (Threshold)
Fires when an atomic rule triggers N times within a time window — like rule 5551 above.
| Element | Purpose |
|---|---|
frequency | How many times the referenced rule must fire |
timeframe | The time window (in seconds) for counting occurrences |
<same_source_ip /> | Group by source IP (count per IP, not total) |
<same_user /> | Group by username |
<if_matched_sid> | Which rule to count |
Composite Rules (Multi-Stage)
The most advanced type — chains multiple different rules in sequence to detect multi-step attack patterns.
<rule id="100001" level="12">
<if_sid>5551</if_sid>
<if_fts>srcip</if_fts>
<description>Brute force attack followed by successful login from same IP</description>
</rule>
This would fire when a brute force alert (5551) is followed by a successful login from the same IP — indicating the brute force may have succeeded.
Rule Chaining Is How Wazuh Detects Attacks, Not Just Events. An individual failed login is an event. Ten failed logins is a pattern. Ten failed logins followed by a success is an attack. Rule chaining enables this escalation from event → pattern → attack by building rules that reference other rules.
Rule Levels: The Severity Assignment
Rule levels aren't arbitrary — they follow a hierarchy that reflects the security significance of the detected event:
| Level Range | Meaning | Rule Design Intent |
|---|---|---|
| 0 | Ignored | Rule exists but doesn't generate alerts (used for filtering) |
| 1-3 | Informational | Normal system activity — logged but no action needed |
| 4-6 | Low | Single suspicious events — worth logging, not worth investigating individually |
| 7-9 | Medium | Anomalous activity — investigate within the hour |
| 10-12 | High | Likely malicious activity — investigate immediately |
| 13-15 | Critical | Active attack or confirmed compromise — all-hands response |
How Levels Interact with Frequency
Notice the escalation pattern:
| Scenario | Rule | Level |
|---|---|---|
| 1 failed SSH login | 5503 | 5 (Low) |
| 10 failed SSH logins from same IP in 2 min | 5551 | 10 (High) |
| Brute force followed by success | Custom | 12 (High) |
| Same pattern + privilege escalation | Custom | 14 (Critical) |
Each step up the chain increases severity because the accumulated evidence is stronger. This is the fundamental principle of detection engineering: context elevates confidence.
Rule Groups and MITRE Mapping
Groups
Groups are tags that categorize rules for filtering and searching:
<group>authentication_failed,sshd,pci_dss_10.2.4,nist_800_53_AU.14,</group>
| Group | Purpose |
|---|---|
authentication_failed | Functional category — what type of event |
sshd | Source — which service generated it |
pci_dss_10.2.4 | Compliance mapping — PCI DSS requirement |
nist_800_53_AU.14 | Compliance mapping — NIST 800-53 control |
Groups let you search broadly. Instead of memorizing dozens of individual rule IDs, you can search rule.groups:authentication_failed to find all failed auth events regardless of source (SSH, Windows, PAM, etc.).
MITRE ATT&CK Mapping
Modern Wazuh rules include ATT&CK technique IDs:
<mitre>
<id>T1110</id>
</mitre>
This maps the rule to ATT&CK T1110 (Brute Force). In the dashboard, you'll see this in the rule.mitre field with the tactic (Credential Access) and technique name. This is what connects your SIEM alerts to the ATT&CK framework you mapped in Lab 1.2.
Rule Tuning: Reducing Noise Without Losing Signal
The most practical skill related to rules is tuning — adjusting rules to reduce false positives while preserving detection of real threats. Poor tuning is the #1 cause of alert fatigue in SOCs.
Why Rules Need Tuning
Out-of-the-box rules are designed to be broadly effective across many environments. But every environment is different:
| Problem | Example | Impact |
|---|---|---|
| Too sensitive | FIM alerts every time a log file rotates | Hundreds of daily FP alerts for /var/log files |
| Wrong context | Brute force rule fires when IT runs password audits | High-severity alerts for authorized activity |
| Missing exclusions | Service accounts trigger "new logon" alerts every 4 hours | Predictable noise that buries real alerts |
| Threshold too low | Rule fires on 3 failures — normal for users mistyping | Alert fatigue from common mistakes |
Tuning Strategies
| Strategy | How | When |
|---|---|---|
| Adjust threshold | Increase frequency from 10 to 20 | FP rate is high but the rule catches real attacks at higher counts |
| Narrow scope | Add <srcip>!10.0.1.0/24</srcip> to exclude internal IPs | Internal scanners trigger the rule |
| Exclude known-good | Add exception for specific service accounts | Automated processes trigger human-focused rules |
| Lower severity | Change level from 10 to 7 | The event is worth logging but doesn't need immediate investigation |
| Time-based exception | Suppress during maintenance windows | Planned changes trigger FIM and new service alerts |
Never Disable a Rule — Tune It. Disabling a rule removes detection entirely. Instead, adjust the threshold, add exclusions, or lower the severity. The goal is to keep the rule active for real threats while suppressing known-good activity. A disabled rule can't catch the one real attack that looks like the false positives you were ignoring.
The Tuning Workflow
- Identify the noisy rule from your dashboard's top rules widget (Lesson 2.3)
- Analyze the false positives — what's triggering them? Same IP? Same user? Same schedule?
- Design an exclusion that covers the FP pattern without blocking real attacks
- Test the tuned rule against historical data — does it still catch the known-good alerts?
- Deploy and monitor — track FP rate for the rule over the next week
- Document the change — what you tuned, why, and what to watch for
Where Rules Live
Wazuh rules are stored in XML files organized by category:
| Path | Contents |
|---|---|
/var/ossec/ruleset/rules/ | Built-in rules (4,000+) — never edit these directly |
/var/ossec/etc/rules/local_rules.xml | Custom rules and overrides — your changes go here |
/var/ossec/ruleset/decoders/ | Built-in decoders — never edit these directly |
/var/ossec/etc/decoders/local_decoder.xml | Custom decoders — your changes go here |
The separation between built-in and local files is critical: when Wazuh updates, the built-in rules are replaced. Any changes you made to built-in files would be lost. Always put customizations in the local_ files.
Reading Rule Files Is a Superpower. When an alert fires and you want to understand exactly why, find the rule file (/var/ossec/ruleset/rules/0095-sshd_rules.xml for SSH rules), search for the rule ID, and read the conditions. This is faster and more accurate than Googling the rule ID — and it works offline.
Putting It Together: Following a Log Through the Pipeline
Let's trace a single event from raw log to alert to reinforce the complete pipeline:
1. Raw log arrives:
Jan 15 06:25:01 linux-web-01 sshd[5150]: Failed password for root from 185.220.101.42 port 45030 ssh2
2. Pre-decoder extracts syslog header:
- Timestamp: Jan 15 06:25:01
- Hostname: linux-web-01
- Program: sshd
3. Parent decoder (sshd) matches based on program_name.
4. Child decoder (sshd-failed) extracts fields:
- user: root
- srcip: 185.220.101.42
- srcport: 45030
5. Rule 5503 matches ("Failed password" string matched) → generates a level 5 internal event.
6. Rule 5551 checks frequency: Has rule 5503 fired 10+ times from the same source IP in the last 120 seconds?
- If yes → Alert generated: Level 10, rule 5551, "sshd: brute force trying to get access to the system."
- If no → Event logged but no brute force alert yet.
7. Alert appears in the dashboard with all decoded fields, the rule description, severity level, MITRE mapping, and the original raw log preserved in full_log.
Key Takeaways
- Decoders parse raw log text into structured fields using regex and extraction patterns — they work in chains (pre-decoder → parent → child)
- Rules evaluate decoded fields against detection logic — atomic rules match single events, frequency rules require thresholds, composite rules chain multiple detections
- Rule 5551 (SSH brute force) requires rule 5503 (single failure) to fire 10 times from the same IP within 120 seconds — this escalation pattern reduces false positives
- Rule levels follow a hierarchy: 0-3 informational, 4-6 low, 7-9 medium, 10-12 high, 13-15 critical
- Rule groups enable broad category searches; MITRE mappings connect rules to the ATT&CK framework
- Tuning rules (adjusting thresholds, adding exclusions, lowering severity) is essential to reduce alert fatigue — never disable a rule entirely
- Custom rules go in
local_rules.xml, custom decoders inlocal_decoder.xml— never edit built-in files
Knowledge Check: Wazuh Rules & Decoders
10 questions · 70% to pass
What is the purpose of a Wazuh decoder in the log processing pipeline?
In rule 5551 (SSH brute force), the XML contains frequency='10' and timeframe='120'. What does this mean in plain English?
A rule fires on every log rotation event for /var/log/*.log files, generating 200 false positive FIM alerts daily. What is the best tuning approach?
Why should custom rules always go in local_rules.xml instead of modifying the built-in rule files?
An analyst sees a level 5 alert (single SSH failure, rule 5503) and a level 10 alert (brute force, rule 5551) from the same IP. What detection engineering principle explains the severity difference?
In a Wazuh decoder, what is the purpose of the <prematch> element?
What type of Wazuh rule would detect: 'SSH brute force from an IP, followed by a successful login from the same IP within 5 minutes'?
In Lab 1.1, you saw rule 5551 fire for the SSH brute force attack. Based on what you now know about rule XML, what is the parent rule that 5551 depends on (via <if_matched_sid>)?
In Lab 1.3, you encountered rule 5902 (new Windows service installed) on WIN-SERVER-01. If this rule fires 50 times daily because IT deploys services frequently, what is the best tuning approach that preserves security detection?
In the lab data, rule 80790 (Windows event log cleared) fires at level 15. Based on the rule level hierarchy in this lesson, why is log clearing classified as critical severity?
0/10 answered