courses

Wazuh SIEM

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

References


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.conf defaults. Built-in Suricata rule IDs and the firewall-drop script path can shift between releases — confirm with wazuh-logtest and 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).