Wazuh SIEM
- Wazuh SIEM
- Turning host events into alerts you can act on
- Architecture
- Agent Registration
- ossec.conf — Agent Configuration
- File Integrity Monitoring
- Rule and Decoder Structure
- Custom Decoder
- Suricata EVE Integration
- Active Response
- Docker Event Monitoring
- Searching Alerts from the CLI
- Alert Level Reference
- Key takeaways
- References
Turning host events into alerts you can act on
Wazuh is an open-source security platform that combines host-based intrusion detection (HIDS), log management, file integrity monitoring (FIM), vulnerability detection, and active response in a single agent-based architecture. It is the monitoring layer of the lab’s Target System.
ℹ️ Detection is only useful if something reads the alert. Wazuh’s value is the pipeline — agent collects, manager decodes and rules match, dashboard surfaces, active response acts. Each custom rule or decoder you add should map to a question you actually want answered (“did anyone brute-force DVWA?”), not just more events in the indexer.
For the broader SIEM/SOC concepts this fits into — log sources, correlation, the analyst workflow — see the SIEM, SOC, and threat detection page.
Architecture
ubuntu-server wazuh (10.10.10.30)
┌─────────────────────┐ ┌──────────────────────────┐
│ wazuh-agent │──1514──▶│ wazuh-manager │
│ (collects events) │ │ (rules, decoders) │
│ │ │ │
│ ossec.conf │ │ wazuh-indexer │
│ /var/ossec/ │ │ (Elasticsearch-based) │
└─────────────────────┘ │ │
│ wazuh-dashboard │
│ https://10.10.10.30 │
└──────────────────────────┘
| Component | Role |
|---|---|
| wazuh-agent | Runs on monitored hosts; collects logs, FIM events, syscall data |
| wazuh-manager | Receives agent data; applies decoders and rules; generates alerts |
| wazuh-indexer | Stores alerts and logs (OpenSearch-based) |
| wazuh-dashboard | Web UI for alert review, rule management, and dashboards |
Communication between agent and manager uses an encrypted channel on TCP/UDP port 1514. The dashboard runs on port 443.
Agent Registration
The agent must be registered with the manager before it sends events. Registration associates a unique agent ID and name with the manager’s address.
On ubuntu-server
# Set the manager address and register
WAZUH_MANAGER='10.10.10.30' \
WAZUH_AGENT_NAME='ubuntu-server' \
dpkg-reconfigure wazuh-agent
# Or edit /var/ossec/etc/ossec.conf manually:
# <server>
# <address>10.10.10.30</address>
# </server>
# Start the agent
systemctl enable --now wazuh-agent
# Verify it is connected
systemctl status wazuh-agent
On the Wazuh manager (10.10.10.30)
# List registered agents
/var/ossec/bin/agent_control -l
# Show agent status
/var/ossec/bin/agent_control -i 001 # replace 001 with your agent ID
In the dashboard: Agents → Active agents should show ubuntu-server with status Active.
ossec.conf — Agent Configuration
The agent’s behavior is controlled by /var/ossec/etc/ossec.conf. Key sections:
<ossec_config>
<!-- Manager connection -->
<client>
<server>
<address>10.10.10.30</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
</client>
<!-- Ingest Suricata EVE JSON (Week 7) -->
<localfile>
<log_format>json</log_format>
<location>/var/log/suricata/eve.json</location>
</localfile>
<!-- Ingest nginx access log -->
<localfile>
<log_format>apache</log_format>
<location>/var/log/nginx/access.log</location>
</localfile>
<!-- Ingest Docker daemon log -->
<localfile>
<log_format>syslog</log_format>
<location>/var/log/syslog</location>
<log_format>docker</log_format>
</localfile>
</ossec_config>
After editing ossec.conf, restart the agent:
systemctl restart wazuh-agent
File Integrity Monitoring
FIM detects when files are created, modified, or deleted. It takes a cryptographic baseline of monitored directories and alerts on any deviation.
Configure FIM in ossec.conf
<syscheck>
<frequency>300</frequency> <!-- check every 5 minutes -->
<scan_on_start>yes</scan_on_start>
<!-- Directories to monitor -->
<directories check_all="yes">/etc</directories>
<directories check_all="yes">/var/www/html</directories>
<directories check_all="yes">/etc/nginx</directories>
<!-- Ignore noisy paths -->
<ignore>/etc/mtab</ignore>
<ignore>/etc/mnttab</ignore>
<ignore>/etc/hosts.deny</ignore>
<ignore type="sregex">.log$|.tmp$</ignore>
</syscheck>
Triggering and viewing a FIM alert
# Modify a monitored file
echo "# test change" >> /etc/nginx/nginx.conf
# Wait up to 5 minutes, or force an immediate check
/var/ossec/bin/agent_control -r -u 001
# On the manager: search for FIM alerts
grep "ossec: Integrity" /var/ossec/logs/alerts/alerts.log | tail -5
In the dashboard: File Integrity Monitoring → Events shows every change with before/after hash.
Rule and Decoder Structure
Decoder pipeline
Raw log → decoder extracts fields → rule matches fields → alert generated
nginx log line:
10.10.10.10 - - [20/May/2026:14:22:01 +0000] "POST /dvwa/login.php HTTP/1.1" 200 1234
↓ nginx decoder extracts:
srcip=10.10.10.10 url=/dvwa/login.php method=POST status=200
↓ rule 31103 matches:
"Web server 200 OK response" level=0
↓ child rule matches repeated 401s before 200:
"Possible web application brute force" level=10 → ALERT
Rule file format
Rules live in /var/ossec/ruleset/rules/ (built-in) and /var/ossec/etc/rules/local_rules.xml (custom). Always write custom rules in local_rules.xml — built-in files are overwritten on upgrades.
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="dvwa,web,authentication">
<!-- Decoder rule: identify DVWA login from nginx log -->
<rule id="100001" level="3">
<decoded_as>nginx</decoded_as>
<url>/dvwa/login.php</url>
<description>DVWA login attempt</description>
</rule>
<!-- Brute-force rule: 5+ failed logins from same IP in 60 seconds -->
<rule id="100002" level="10" frequency="5" timeframe="60">
<if_matched_sid>100001</if_matched_sid>
<same_source_ip />
<description>DVWA login brute force detected from $(srcip)</description>
</rule>
</group>
Rule field reference
| Field | Meaning | Example |
|---|---|---|
id |
Unique rule ID; 100000–109999 for custom rules | id="100001" |
level |
Alert severity 0–15; level ≥ 7 triggers most integrations | level="10" |
frequency |
Event count threshold for frequency-based rules | frequency="5" |
timeframe |
Window in seconds for frequency counting | timeframe="60" |
if_matched_sid |
Trigger when parent rule fires | <if_matched_sid>100001</if_matched_sid> |
same_source_ip |
Frequency counter keyed by source IP | <same_source_ip /> |
decoded_as |
Restrict to events decoded by a specific decoder | <decoded_as>nginx</decoded_as> |
match |
Literal string match against log | <match>Failed password</match> |
regex |
PCRE-style regex match | <regex>Failed \S+ for</regex> |
Test rules without restarting
# Test a specific log line against all rules
echo '10.10.10.10 - - [20/May/2026:14:22:01 +0000] "POST /dvwa/login.php HTTP/1.1" 401 0' \
| /var/ossec/bin/wazuh-logtest
The output shows which decoder parsed it and which rules matched.
Custom Decoder
When Wazuh doesn’t know the log format, write a decoder. Custom decoders go in /var/ossec/etc/decoders/local_decoder.xml.
Example: DVWA login log format
DVWA logs PHP errors to /var/log/dvwa/error.log in a non-standard format:
[2026-05-20 14:22:01] DVWA LOGIN: user=admin ip=10.10.10.10 result=failed
<!-- /var/ossec/etc/decoders/local_decoder.xml -->
<decoder name="dvwa-login">
<prematch>DVWA LOGIN:</prematch>
<regex offset="after_prematch">user=(\S+) ip=(\S+) result=(\S+)</regex>
<order>user, srcip, login_result</order>
</decoder>
<decoder name="dvwa-login-success">
<parent>dvwa-login</parent>
<regex>result=success</regex>
<order>login_result</order>
</decoder>
Test the decoder:
echo '[2026-05-20 14:22:01] DVWA LOGIN: user=admin ip=10.10.10.10 result=failed' \
| /var/ossec/bin/wazuh-logtest
# Should show: decoder matched 'dvwa-login', fields: user=admin srcip=10.10.10.10
Suricata EVE Integration
Wazuh ships rule ID 86600–86699 for Suricata. Once the localfile stanza points at eve.json, Suricata alerts appear in the dashboard automatically.
<!-- In /var/ossec/etc/ossec.conf on ubuntu-server -->
<localfile>
<log_format>json</log_format>
<location>/var/log/suricata/eve.json</location>
</localfile>
systemctl restart wazuh-agent
# Trigger a Suricata alert (e.g. nmap from Kali)
# Then search in the Wazuh dashboard:
# Filter: rule.groups: suricata
Active Response
Active response runs a script automatically when a rule fires above a configured threshold. The most common use: block a brute-force source IP.
Configure firewall-drop active response
The firewall-drop script (included with Wazuh) adds a DROP rule for the source IP using the system firewall (nftables or iptables).
<!-- In /var/ossec/etc/ossec.conf on the MANAGER -->
<ossec_config>
<active-response>
<command>firewall-drop</command>
<location>local</location> <!-- run on the agent that generated the alert -->
<rules_id>100002</rules_id> <!-- our DVWA brute-force rule -->
<timeout>3600</timeout> <!-- unban after 1 hour -->
</active-response>
</ossec_config>
# Restart the manager after editing
systemctl restart wazuh-manager
# Verify active response is enabled
/var/ossec/bin/agent_control -i 001 | grep active-response
Testing active response
From Kali, trigger the brute-force rule (5+ POST /dvwa/login.php in 60 seconds). On ubuntu-server:
# Check the active response log
tail -f /var/ossec/logs/active-responses.log
# Check that the nftables/iptables block was added
nft list ruleset | grep 10.10.10.10
# or
iptables -L -n | grep 10.10.10.10
Docker Event Monitoring
Wazuh can monitor Docker events via the docker-listener wodle (module):
<!-- In /var/ossec/etc/ossec.conf on ubuntu-server -->
<wodle name="docker-listener">
<interval>10m</interval>
<attempts>5</attempts>
<run_on_start>yes</run_on_start>
<disabled>no</disabled>
</wodle>
This captures: container start/stop/kill, image pull, exec events. An unexpected docker run during off-hours is a strong indicator of compromise.
Searching Alerts from the CLI
# On the Wazuh manager — search the current day's alerts
grep -i "brute" /var/ossec/logs/alerts/alerts.log
# JSON format (more parseable)
grep -i "brute" /var/ossec/logs/alerts/alerts.json | jq .rule.description
# Filter by rule level (level 10+)
cat /var/ossec/logs/alerts/alerts.json \
| jq 'select(.rule.level >= 10) | {time: .timestamp, rule: .rule.description, agent: .agent.name}'
Alert Level Reference
| Level | Meaning | Typical triggers |
|---|---|---|
| 0–3 | Informational | Normal events, successful logins |
| 4–6 | Low | Failed logins, config changes |
| 7–9 | Medium | Multiple failures, unusual activity |
| 10–12 | High | Brute force, rootkit indicators |
| 13–15 | Critical | Active attack, rootkit confirmed |
Active response and email alerts typically trigger at level 7 or above.
Key takeaways
- Wazuh is agent-based: the agent collects logs/FIM/syscall data, the manager decodes and applies rules, and the indexer plus dashboard make alerts searchable.
- Register the agent before it can report, and point
ossec.confat the log sources you care about (Suricataeve.json, nginx, Docker, syslog). - Write custom rules and decoders in
local_rules.xml/local_decoder.xml— built-in files are overwritten on upgrade — and validate them withwazuh-logtestbefore restarting. - File Integrity Monitoring baselines directories and alerts on any change; active response (e.g.
firewall-drop) turns a high-severity rule into an automatic, time-limited IP block. - Alert levels run 0–15; level 7+ typically drives email and active response, so tune rule levels to match the action you want.
References
- Wazuh Documentation — https://documentation.wazuh.com/current/
- Wazuh Rules Syntax — https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/rules.html
- Wazuh Decoders Syntax — https://documentation.wazuh.com/current/user-manual/ruleset/ruleset-xml-syntax/decoders.html
- Wazuh Active Response — https://documentation.wazuh.com/current/user-manual/capabilities/active-response/
- Wazuh File Integrity Monitoring — https://documentation.wazuh.com/current/user-manual/capabilities/file-integrity/
- Suricata EVE JSON integration — https://documentation.wazuh.com/current/proof-of-concept-guide/integrate-network-ids-suricata.html
Related course pages: SIEM, SOC & threat detection · Suricata IDS/IPS · Incident response
🛠️ Maintenance note: Wazuh’s indexer and dashboard are OpenSearch-based (the Elastic-based stack was replaced); version against the current docs since major releases change package names and
ossec.confdefaults. Built-in Suricata rule IDs and thefirewall-dropscript path can shift between releases — confirm withwazuh-logtestand the active-response log rather than assuming. The agent↔manager channel is TCP/UDP 1514; the dashboard moved to HTTPS on 443 (older builds used 5601).