Wazuh as SIEM: Building a Security Operations Center

Introduction

A proper Security Information and Event Management (SIEM) system is no longer optional—it’s a requirement for compliance and a necessity for detecting threats. After evaluating several solutions, I chose Wazuh as our primary SIEM. It’s open source, incredibly capable, and integrates well with existing infrastructure.

This guide covers deploying Wazuh in production, creating detection rules, and building effective security workflows.

Why Wazuh?

CapabilityWazuhSplunkELK Stack
License CostFree$$$$Free (basic)
Agent-based CollectionYesYesRequires Beats
File Integrity MonitoringBuilt-inAdd-onAdd-on
Vulnerability DetectionBuilt-inAdd-onNo
Compliance ReportingBuilt-inAdd-onNo
Learning CurveModerateHighHigh

Wazuh combines HIDS, log analysis, vulnerability detection, and compliance monitoring in one platform—impressive for a free tool.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                     Wazuh Dashboard                         │
│                  (Kibana + Wazuh Plugin)                    │
└─────────────────────────┬───────────────────────────────────┘

┌─────────────────────────▼───────────────────────────────────┐
│                    Wazuh Indexer                            │
│                (OpenSearch Cluster)                         │
└─────────────────────────┬───────────────────────────────────┘

┌─────────────────────────▼───────────────────────────────────┐
│                    Wazuh Manager                            │
│         (Rules Engine, API, Agent Management)               │
└─────────────────────────┬───────────────────────────────────┘

     ┌────────────────────┼────────────────────┐
     │                    │                    │
┌────▼────┐         ┌─────▼─────┐        ┌────▼────┐
│  Agent  │         │   Agent   │        │ Syslog  │
│ (Linux) │         │ (Windows) │        │ Sources │
└─────────┘         └───────────┘        └─────────┘

Installation

Single-Node Deployment

For environments up to 100 agents:

# Download and run the installation assistant
curl -sO https://packages.wazuh.com/4.7/wazuh-install.sh
chmod +x wazuh-install.sh
./wazuh-install.sh -a

Distributed Deployment

For larger environments, deploy components separately:

# Node 1: Wazuh Indexer
./wazuh-install.sh --wazuh-indexer node-1

# Node 2: Wazuh Manager  
./wazuh-install.sh --wazuh-server wazuh-1

# Node 3: Wazuh Dashboard
./wazuh-install.sh --wazuh-dashboard dashboard

Initial Configuration

# /var/ossec/etc/ossec.conf (key sections)

<ossec_config>
  <global>
    <email_notification>yes</email_notification>
    <smtp_server>smtp.yourorg.com</smtp_server>
    <email_from>wazuh@yourorg.com</email_from>
    <email_to>security@yourorg.com</email_to>
    <email_maxperhour>12</email_maxperhour>
  </global>

  <alerts>
    <log_alert_level>3</log_alert_level>
    <email_alert_level>10</email_alert_level>
  </alerts>

  <remote>
    <connection>secure</connection>
    <port>1514</port>
    <protocol>tcp</protocol>
  </remote>
</ossec_config>

Agent Deployment

Linux Agents

# Debian/Ubuntu
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | gpg --dearmor > /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" > /etc/apt/sources.list.d/wazuh.list
apt update && apt install wazuh-agent -y

# Configure agent
cat > /var/ossec/etc/ossec.conf << 'EOF'
<ossec_config>
  <client>
    <server>
      <address>wazuh.yourorg.com</address>
      <port>1514</port>
      <protocol>tcp</protocol>
    </server>
    <enrollment>
      <enabled>yes</enabled>
      <manager_address>wazuh.yourorg.com</manager_address>
      <agent_name>webserver-01</agent_name>
      <groups>linux,webservers,production</groups>
    </enrollment>
  </client>
</ossec_config>
EOF

systemctl enable wazuh-agent
systemctl start wazuh-agent

Windows Agents

# Download and install
Invoke-WebRequest -Uri "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.7.0-1.msi" -OutFile wazuh-agent.msi
msiexec.exe /i wazuh-agent.msi /q WAZUH_MANAGER="wazuh.yourorg.com" WAZUH_AGENT_GROUP="windows,workstations"

# Start service
NET START WazuhSvc

Automated Deployment with Ansible

# playbooks/deploy-wazuh-agent.yml
---
- name: Deploy Wazuh Agent
  hosts: all
  become: yes
  vars:
    wazuh_manager: "wazuh.yourorg.com"
    
  tasks:
    - name: Add Wazuh repository (Debian)
      when: ansible_os_family == "Debian"
      block:
        - name: Add GPG key
          apt_key:
            url: https://packages.wazuh.com/key/GPG-KEY-WAZUH
            state: present
            
        - name: Add repository
          apt_repository:
            repo: "deb https://packages.wazuh.com/4.x/apt/ stable main"
            state: present
            
        - name: Install agent
          apt:
            name: wazuh-agent
            state: present
            update_cache: yes

    - name: Configure agent
      template:
        src: ossec.conf.j2
        dest: /var/ossec/etc/ossec.conf
      notify: Restart wazuh-agent
      
    - name: Enable and start agent
      service:
        name: wazuh-agent
        enabled: yes
        state: started

  handlers:
    - name: Restart wazuh-agent
      service:
        name: wazuh-agent
        state: restarted

Detection Rules

Understanding Rule Structure

<!-- /var/ossec/etc/rules/local_rules.xml -->

<group name="custom,">
  <!-- SSH brute force detection -->
  <rule id="100001" level="10" frequency="5" timeframe="120">
    <if_matched_sid>5716</if_matched_sid>
    <same_source_ip />
    <description>SSH brute force attempt from $(srcip)</description>
    <group>authentication_failures,</group>
  </rule>

  <!-- Suspicious process execution -->
  <rule id="100002" level="12">
    <if_sid>530</if_sid>
    <match>nc -e|nc.traditional|ncat -e|bash -i</match>
    <description>Possible reverse shell detected</description>
    <group>attack,shell,</group>
  </rule>

  <!-- Privilege escalation attempt -->
  <rule id="100003" level="14">
    <if_sid>5402</if_sid>
    <user>^(?!root).*</user>
    <match>sudo su -|sudo -i|sudo bash</match>
    <description>User $(user) attempted privilege escalation</description>
    <group>privilege_escalation,</group>
  </rule>
</group>

File Integrity Monitoring

<!-- /var/ossec/etc/ossec.conf -->

<syscheck>
  <frequency>43200</frequency>
  <scan_on_start>yes</scan_on_start>
  
  <!-- Critical system directories -->
  <directories check_all="yes" realtime="yes">/etc,/usr/bin,/usr/sbin</directories>
  <directories check_all="yes" realtime="yes">/bin,/sbin</directories>
  
  <!-- Web application files -->
  <directories check_all="yes" realtime="yes" report_changes="yes">/var/www</directories>
  
  <!-- Configuration files -->
  <directories check_all="yes" realtime="yes">/etc/ssh</directories>
  <directories check_all="yes" realtime="yes">/etc/pam.d</directories>
  
  <!-- Ignore noisy directories -->
  <ignore>/etc/mtab</ignore>
  <ignore>/etc/resolv.conf</ignore>
  <ignore type="sregex">.log$|.swp$</ignore>
</syscheck>

Custom Decoders

<!-- /var/ossec/etc/decoders/local_decoder.xml -->

<!-- Custom application log decoder -->
<decoder name="custom-app">
  <program_name>myapp</program_name>
</decoder>

<decoder name="custom-app-auth">
  <parent>custom-app</parent>
  <regex>Authentication (failed|success) for user (\S+) from (\S+)</regex>
  <order>status, user, srcip</order>
</decoder>

<decoder name="custom-app-error">
  <parent>custom-app</parent>
  <regex>ERROR: (\S+) - (.+)</regex>
  <order>error_code, error_message</order>
</decoder>

Vulnerability Detection

Configure Vulnerability Scanner

<!-- /var/ossec/etc/ossec.conf -->

<vulnerability-detector>
  <enabled>yes</enabled>
  <interval>5m</interval>
  <min_full_scan_interval>6h</min_full_scan_interval>
  <run_on_start>yes</run_on_start>

  <!-- Operating system CVE feeds -->
  <provider name="canonical">
    <enabled>yes</enabled>
    <os>focal</os>
    <os>jammy</os>
    <update_interval>1h</update_interval>
  </provider>

  <provider name="redhat">
    <enabled>yes</enabled>
    <update_interval>1h</update_interval>
  </provider>

  <provider name="nvd">
    <enabled>yes</enabled>
    <update_interval>1h</update_interval>
  </provider>
</vulnerability-detector>

Vulnerability Reporting

# Query vulnerabilities via API
curl -k -u admin:admin -X GET "https://localhost:55000/vulnerability/agent/001" | jq '.data.affected_items[] | {cve: .cve, severity: .severity, package: .package.name}'

Compliance Monitoring

PCI-DSS Configuration

<!-- Enable PCI-DSS checks -->
<wodle name="sca">
  <enabled>yes</enabled>
  <scan_on_start>yes</scan_on_start>
  <interval>12h</interval>
  
  <policies>
    <policy>cis_ubuntu22-04.yml</policy>
    <policy>pci_dss_v3.2.1.yml</policy>
  </policies>
</wodle>

Custom Compliance Policy

# /var/ossec/etc/shared/custom_policy.yml

policy:
  id: "custom_security_policy"
  file: "custom_security_policy.yml"
  name: "Custom Security Baseline"
  description: "Organization-specific security requirements"

requirements:
  title: "SSH Configuration"
  description: "Verify SSH is properly configured"
  
checks:
  - id: 10001
    title: "SSH root login disabled"
    condition: all
    rules:
      - "f:/etc/ssh/sshd_config -> r:^PermitRootLogin no$"
      
  - id: 10002
    title: "SSH protocol version 2"
    condition: all
    rules:
      - "f:/etc/ssh/sshd_config -> r:^Protocol 2$"
      
  - id: 10003
    title: "Password authentication disabled"
    condition: all
    rules:
      - "f:/etc/ssh/sshd_config -> r:^PasswordAuthentication no$"

Active Response

Automatic IP Blocking

<!-- /var/ossec/etc/ossec.conf -->

<active-response>
  <command>firewall-drop</command>
  <location>local</location>
  <rules_id>100001</rules_id>  <!-- SSH brute force -->
  <timeout>3600</timeout>
</active-response>

<active-response>
  <command>firewall-drop</command>
  <location>local</location>
  <level>12</level>  <!-- High severity events -->
  <timeout>7200</timeout>
</active-response>

Custom Response Script

#!/bin/bash
# /var/ossec/active-response/bin/slack-alert.sh

ALERT_FILE=$1
SRCIP=$(cat $ALERT_FILE | grep srcip | cut -d: -f2)
RULE=$(cat $ALERT_FILE | grep rule | cut -d: -f2)

curl -X POST -H 'Content-type: application/json' \
  --data "{\"text\":\"🚨 Security Alert: Rule $RULE triggered from $SRCIP\"}" \
  https://hooks.slack.com/services/YOUR/WEBHOOK
<!-- Register custom command -->
<command>
  <name>slack-alert</name>
  <executable>slack-alert.sh</executable>
  <timeout_allowed>no</timeout_allowed>
</command>

Integration with External Tools

TheHive Integration

#!/usr/bin/env python3
# /var/ossec/integrations/thehive.py

import json
import requests
import sys

THEHIVE_URL = "https://thehive.yourorg.com"
THEHIVE_KEY = "your-api-key"

def create_alert(alert_data):
    headers = {
        "Authorization": f"Bearer {THEHIVE_KEY}",
        "Content-Type": "application/json"
    }
    
    alert = {
        "title": f"Wazuh: {alert_data['rule']['description']}",
        "description": json.dumps(alert_data, indent=2),
        "severity": map_severity(alert_data['rule']['level']),
        "source": "wazuh",
        "sourceRef": alert_data['id'],
        "type": "wazuh-alert"
    }
    
    response = requests.post(
        f"{THEHIVE_URL}/api/alert",
        headers=headers,
        json=alert
    )
    return response.status_code

def map_severity(level):
    if level >= 12:
        return 3  # High
    elif level >= 8:
        return 2  # Medium
    return 1  # Low

if __name__ == "__main__":
    alert_data = json.load(sys.stdin)
    create_alert(alert_data)

Lessons Learned

  1. Start with defaults, then tune. Wazuh’s default rules are good—don’t disable them before understanding what they catch.
  2. Agent groups simplify management. Group agents by OS, role, and environment for targeted configuration.
  3. File integrity monitoring needs baselines. Run FIM after changes, not during, or you’ll drown in alerts.
  4. Active response requires caution. Auto-blocking can cause outages if rules are too aggressive.
  5. Integrate early. Connect Wazuh to your incident response workflow from day one.

Conclusion

Wazuh provides enterprise-grade security monitoring without the enterprise price tag. The initial setup requires investment, but the result is comprehensive visibility into your security posture.

Start with basic host monitoring, add custom rules as you understand your environment, and gradually enable active response. Security is a journey, not a destination.

Resources