Wazuh EDR/XDR
Wazuh is an open-source security platform that provides unified XDR and SIEM protection for endpoints and cloud workloads. It delivers threat detection, integrity monitoring, incident response, and compliance monitoring across on-premises, cloud, and container environments.
Wazuh Architecture
Wazuh 4.x uses a distributed architecture with four main components:
| Component | Function | Deployment |
|---|---|---|
| Wazuh Server | Analyzes agent data, decodes events, matches against rules, triggers alerts | Linux server or container |
| Wazuh Indexer | Full-text search and analytics engine (OpenSearch fork) | Cluster for HA |
| Wazuh Dashboard | Web-based security analytics and visualization (OpenSearch Dashboards) | Web server |
| Wazuh Agent | Endpoint sensor collecting logs, file integrity, configuration data | Linux, Windows, macOS |
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Wazuh Server โ
โ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ Analysisd โ โ Remoted โ โ Execd โ โ
โ โ (Decoders) โ โ (Agent comm)โ โ (Active resp)โ โ
โ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ โโโโโโโโฌโโโโโโโโ โ
โ โ โ โ โ
โ โโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโ โ
โ โ Rules Engine โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโ
โผ โผ โผ
โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ
โ Agent 1 โ โ Agent 2 โ โ Agent N โ
โ (Linux) โ โ(Windows) โ โ (macOS) โ
โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ
โ โ โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ Wazuh Indexer โ
โ (OpenSearch) โ
โโโโโโโโโโฌโโโโโโโโโโ
โผ
โโโโโโโโโโโโโโโโโโโโ
โ Wazuh Dashboard โ
โ (Web UI/Kibana) โ
โโโโโโโโโโโโโโโโโโโโ
Deployment Options
Docker Compose Deployment (All-in-One)
# docker-compose.wazuh.yml
version: "3.8"
services:
wazuh-manager:
image: wazuh/wazuh-manager:4.7.2
container_name: wazuh-manager
hostname: wazuh-manager
restart: unless-stopped
ports:
- "1514:1514" # Agent syslog
- "1515:1515" # Agent enrollment
- "514:514/udp" # Syslog UDP
- "55000:55000" # Wazuh API
environment:
- INDEXER_URL=https://wazuh-indexer:9200
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=${INDEXER_PASSWORD}
- FILEBEAT_SSL_VERIFICATION_MODE=full
- SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
- SSL_CERTIFICATE=/etc/ssl/filebeat.pem
- SSL_KEY=/etc/ssl/filebeat.key
- API_USERNAME=wazuh-wui
- API_PASSWORD=${API_PASSWORD}
volumes:
- wazuh_api_config:/var/ossec/api/configuration
- wazuh_etc:/var/ossec/etc
- wazuh_logs:/var/ossec/logs
- wazuh_queue:/var/ossec/queue
- wazuh_var_multigroups:/var/ossec/var/multigroups
- wazuh_integrations:/var/ossec/integrations
- wazuh_active_response:/var/ossec/active-response/bin
- wazuh_agentless:/var/ossec/agentless
- wazuh_wodles:/var/ossec/wodles
- wazuh_filebeat:/etc/filebeat
- ./certs/root-ca.pem:/etc/ssl/root-ca.pem:ro
- ./certs/filebeat.pem:/etc/ssl/filebeat.pem:ro
- ./certs/filebeat.key:/etc/ssl/filebeat.key:ro
networks:
- wazuh
wazuh-indexer:
image: wazuh/wazuh-indexer:4.7.2
container_name: wazuh-indexer
hostname: wazuh-indexer
restart: unless-stopped
ports:
- "9200:9200"
environment:
- OPENSEARCH_JAVA_OPTS="-Xms2g -Xmx2g"
- bootstrap.memory_lock=true
- NODE_NAME=wazuh-indexer
- CLUSTER_NAME=wazuh-cluster
- CLUSTER_INITIAL_MASTER_NODES=wazuh-indexer
- DISCOVERY_SEED_HOSTS=wazuh-indexer
- plugins.security.ssl.http.enabled=true
- plugins.security.ssl.http.pemcert_filepath=/usr/share/wazuh-indexer/certs/indexer.pem
- plugins.security.ssl.http.pemkey_filepath=/usr/share/wazuh-indexer/certs/indexer.key
- plugins.security.ssl.http.pemtrustedcas_filepath=/usr/share/wazuh-indexer/certs/root-ca.pem
- plugins.security.ssl.transport.enabled=true
- plugins.security.ssl.transport.pemcert_filepath=/usr/share/wazuh-indexer/certs/indexer.pem
- plugins.security.ssl.transport.pemkey_filepath=/usr/share/wazuh-indexer/certs/indexer.key
- plugins.security.ssl.transport.pemtrustedcas_filepath=/usr/share/wazuh-indexer/certs/root-ca.pem
- plugins.security.allow_default_init_securityindex=true
- plugins.security.authcz.admin_dn="CN=admin,OU=Wazuh,O=Wazuh,L=California,C=US"
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
volumes:
- wazuh_indexer_data:/var/lib/wazuh-indexer
- ./certs/root-ca.pem:/usr/share/wazuh-indexer/certs/root-ca.pem:ro
- ./certs/indexer.pem:/usr/share/wazuh-indexer/certs/indexer.pem:ro
- ./certs/indexer.key:/usr/share/wazuh-indexer/certs/indexer.key:ro
- ./certs/admin.pem:/usr/share/wazuh-indexer/certs/admin.pem:ro
- ./certs/admin.key:/usr/share/wazuh-indexer/certs/admin.key:ro
networks:
- wazuh
wazuh-dashboard:
image: wazuh/wazuh-dashboard:4.7.2
container_name: wazuh-dashboard
hostname: wazuh-dashboard
restart: unless-stopped
ports:
- "443:5601"
environment:
- INDEXER_USERNAME=admin
- INDEXER_PASSWORD=${INDEXER_PASSWORD}
- WAZUH_API_URL=https://wazuh-manager
- API_USERNAME=wazuh-wui
- API_PASSWORD=${API_PASSWORD}
volumes:
- ./certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:ro
- ./certs/dashboard.pem:/usr/share/wazuh-dashboard/certs/dashboard.pem:ro
- ./certs/dashboard.key:/usr/share/wazuh-dashboard/certs/dashboard.key:ro
depends_on:
- wazuh-indexer
- wazuh-manager
networks:
- wazuh
volumes:
wazuh_api_config:
wazuh_etc:
wazuh_logs:
wazuh_queue:
wazuh_var_multigroups:
wazuh_integrations:
wazuh_active_response:
wazuh_agentless:
wazuh_wodles:
wazuh_filebeat:
wazuh_indexer_data:
networks:
wazuh:
driver: bridge
Certificate Generation
#!/bin/bash
# generate-wazuh-certs.sh โ Generate certificates for Wazuh deployment
cd /opt/wazuh/certs || exit 1
# Root CA
openssl genrsa -out root-ca.key 2048
openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 3650 \
-subj "/C=US/ST=California/L=San Francisco/O=Company/CN=Wazuh Root CA" \
-out root-ca.pem
# Function to generate a certificate
generate_cert() {
local name=$1
local subject=$2
openssl genrsa -out "${name}.key" 2048
openssl req -new -key "${name}.key" \
-subj "$subject" \
-out "${name}.csr"
openssl x509 -req -in "${name}.csr" \
-CA root-ca.pem -CAkey root-ca.key \
-CAcreateserial -out "${name}.pem" \
-days 365 -sha256
}
generate_cert "indexer" "/C=US/ST=California/L=San Francisco/O=Company/OU=Wazuh/CN=wazuh-indexer"
generate_cert "dashboard" "/C=US/ST=California/L=San Francisco/O=Company/OU=Wazuh/CN=wazuh-dashboard"
generate_cert "filebeat" "/C=US/ST=California/L=San Francisco/O=Company/OU=Wazuh/CN=wazuh-manager"
generate_cert "admin" "/C=US/ST=California/L=San Francisco/O=Company/OU=Wazuh/CN=admin"
# Set permissions
chmod 644 *.pem
chmod 600 *.key
rm -f *.csr
echo "Certificates generated in $(pwd)"
Agent Installation
Linux Agent
# RHEL/CentOS/Amazon Linux 2
curl -o wazuh-agent-4.7.2-1.x86_64.rpm \
https://packages.wazuh.com/4.x/yum/wazuh-agent-4.7.2-1.x86_64.rpm
sudo WAZUH_MANAGER='wazuh-manager.internal' WAZUH_AGENT_NAME='$(hostname)' \
rpm -ihv wazuh-agent-4.7.2-1.x86_64.rpm
sudo systemctl daemon-reload
sudo systemctl enable wazuh-agent
sudo systemctl start wazuh-agent
# Ubuntu/Debian
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo gpg --no-default-keyring --keyring gnupg-ring:/usr/share/keyrings/wazuh.gpg --import
sudo chmod 644 /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" | \
sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt update
sudo WAZUH_MANAGER='wazuh-manager.internal' WAZUH_AGENT_NAME='$(hostname)' \
apt install wazuh-agent
# Custom ossec.conf for centralized configuration
sudo tee /var/ossec/etc/ossec.conf > /dev/null <<'EOF'
<ossec_config>
<client>
<server>
<address>wazuh-manager.internal</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<config-profile>ubuntu, ubuntu22, webserver</config-profile>
<notify_time>10</notify_time>
<time-reconnect>60</time-reconnect>
<auto_restart>yes</auto_restart>
<crypto_method>aes</crypto_method>
</client>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/syslog</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/auth.log</location>
</localfile>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/kern.log</location>
</localfile>
<localfile>
<log_format>json</log_format>
<location>/var/log/app/application.json</location>
</localfile>
<!-- File Integrity Monitoring -->
<syscheck>
<directories check_all="yes" report_changes="yes" realtime="yes">
/etc,/usr/bin,/usr/sbin
</directories>
<directories check_all="yes" report_changes="yes" restrict="etc">
/etc/shadow,/etc/passwd,/etc/group
</directories>
<directories check_all="yes" report_changes="yes">
/var/www,/opt/app
</directories>
<ignore>/etc/mtab</ignore>
<ignore>/etc/prelink.cache</ignore>
<ignore>/etc/resolv.conf</ignore>
<ignore>/etc/hdparam</ignore>
<ignore>/etc/udev/devices</ignore>
<ignore>/etc/network/run</ignore>
<frequency>43200</frequency>
</syscheck>
<!-- Vulnerability Detection -->
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<scan_on_start>yes</scan_on_start>
<hardware>yes</hardware>
<os>yes</os>
<network>yes</network>
<packages>yes</packages>
<ports all="no">yes</ports>
<processes>yes</processes>
</wodle>
<!-- Rootcheck -->
<rootcheck>
<disabled>no</disabled>
<check_files>yes</check_files>
<check_trojans>yes</check_trojans>
<check_dev>yes</check_dev>
<check_sys>yes</check_sys>
<check_pids>yes</check_pids>
<check_ports>yes</check_ports>
<check_if>yes</check_if>
<frequency>43200</frequency>
</rootcheck>
</ossec_config>
EOF
sudo systemctl restart wazuh-agent
Windows Agent
# PowerShell installation script
$WazuhManager = "wazuh-manager.internal"
$AgentName = $env:COMPUTERNAME
$DownloadUrl = "https://packages.wazuh.com/4.x/windows/wazuh-agent-4.7.2-1.msi"
# Download
Invoke-WebRequest -Uri $DownloadUrl -OutFile "$env:TEMP\wazuh-agent.msi"
# Install
Start-Process msiexec.exe -ArgumentList "/i","$env:TEMP\wazuh-agent.msi", "/qn", "WAZUH_MANAGER=$WazuhManager", "WAZUH_AGENT_NAME=$AgentName" -Wait
# Configure
$Config = @"
<ossec_config>
<client>
<server>
<address>$WazuhManager</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
<crypto_method>aes</crypto_method>
</client>
<localfile>
<location>Application</location>
<log_format>eventchannel</log_format>
</localfile>
<localfile>
<location>Security</location>
<log_format>eventchannel</log_format>
<query>Event/System[EventID != 5145 and EventID != 5156]</query>
</localfile>
<localfile>
<location>System</location>
<log_format>eventchannel</log_format>
</localfile>
<localfile>
<location>Microsoft-Windows-Sysmon/Operational</location>
<log_format>eventchannel</log_format>
</localfile>
<syscheck>
<directories check_all="yes" report_changes="yes" realtime="yes">
C:\Windows\System32\drivers\etc
</directories>
<directories check_all="yes" report_changes="yes">
C:\inetpub\wwwroot
</directories>
<windows_registry arch="both">
HKEY_LOCAL_MACHINE\Software\Classes\batfile
</windows_registry>
<windows_registry arch="both">
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run
</windows_registry>
<frequency>7200</frequency>
</syscheck>
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<os>yes</os>
<packages>yes</packages>
<hotfixes>yes</hotfixes>
</wodle>
</ossec_config>
"@
Set-Content -Path "C:\Program Files (x86)\ossec-agent\ossec.conf" -Value $Config
# Start service
Start-Service -Name "Wazuh"
Set-Service -Name "Wazuh" -StartupType Automatic
# Verify
Get-Service -Name "Wazuh"
& "C:\Program Files (x86)\ossec-agent\agent-auth.exe" -m $WazuhManager
macOS Agent
# Download and install
curl -o wazuh-agent.pkg \
https://packages.wazuh.com/4.x/macos/wazuh-agent-4.7.2-1.intel64.pkg
sudo installer -pkg wazuh-agent.pkg -target /
# Configure
sudo tee /Library/Ossec/etc/ossec.conf > /dev/null <<'EOF'
<ossec_config>
<client>
<server>
<address>wazuh-manager.internal</address>
<port>1514</port>
<protocol>tcp</protocol>
</server>
</client>
<localfile>
<log_format>syslog</log_format>
<location>/var/log/system.log</location>
</localfile>
<syscheck>
<directories check_all="yes" realtime="yes">/etc,/usr/bin</directories>
</syscheck>
</ossec_config>
EOF
# Start
sudo /Library/Ossec/bin/wazuh-control start
Rules and Decoders Customization
Custom Detection Rules
<!-- /var/ossec/etc/rules/local_rules.xml -->
<group name="custom,application,">
<!-- Detect failed authentication bursts -->
<rule id="100001" level="10" frequency="8" timeframe="120">
<if_matched_sid>100002</if_matched_sid>
<description>Multiple failed login attempts for user: $(dstuser)</description>
<mitre>
<id>T1110</id>
<id>T1110.001</id>
</mitre>
<group>authentication_failed,pci_dss_10.2.4,pci_dss_10.2.5,gpg13_7.1,gdpr_IV_35.7.d,gdpr_IV_32.2,hipaa_164.312.b,nist_800_53_AU.14,nist_800_53_AC.7,</group>
</rule>
<rule id="100002" level="5">
<decoded_as>json</decoded_as>
<field name="event_type">authentication_failed</field>
<description>Application authentication failed</description>
<group>authentication_failed,</group>
</rule>
<!-- Detect privilege escalation via sudo -->
<rule id="100010" level="10">
<if_sid>5402</if_sid>
<match>root</match>
<description>Sudo escalation to root detected for user: $(srcuser)</description>
<mitre>
<id>T1548</id>
<id>T1548.003</id>
</mitre>
<group>pci_dss_10.2.5,pci_dss_10.2.2,gpg13_7.6,gpg13_7.8,gpg13_7.13,gdpr_IV_32.2,hipaa_164.312.b,nist_800_53_AU.6,nist_800_53_AC.6,</group>
</rule>
<!-- Detect suspicious file modifications in web root -->
<rule id="100020" level="12">
<if_group> syscheck </if_group>
<field name="file" type="pcre2">^/var/www/html/.*\.(php|jsp|asp|aspx|sh)$</field>
<description>Suspicious file modification in web root: $(file)</description>
<mitre>
<id>T1505</id>
<id>T1505.003</id>
</mitre>
<group>pci_dss_11.5,gpg13_4.11,gdpr_IV_35.7.d,hipaa_164.312.c.1,hipaa_164.312.c.2,nist_800_53_SI.7,trojan_activity,</group>
</rule>
<!-- Detect outbound connections to known C2 IPs -->
<rule id="100030" level="14">
<if_group> firewall </if_group>
<list field="dstip" lookup="address_match_key">etc/lists/c2-blacklist</list>
<description>Outbound connection to known C2 server: $(dstip)</description>
<mitre>
<id>T1071</id>
<id>T1071.001</id>
</mitre>
<group>pci_dss_1.3.1,gpg13_4.12,gdpr_IV_35.7.d,hipaa_164.312.a.1,nist_800_53_AC.4,nist_800_53_SC.7,nist_800_53_SI.4,</group>
</rule>
<!-- API rate limiting violation -->
<rule id="100040" level="8" frequency="20" timeframe="60">
<if_matched_sid>100041</if_matched_sid>
<description>API rate limit exceeded by IP: $(srcip) โ Possible DoS or scraping</description>
<mitre>
<id>T1498</id>
<id>T1498.001</id>
</mitre>
<group>pci_dss_11.4,gpg13_4.2,gdpr_IV_35.7.d,nist_800_53_SI.4,</group>
</rule>
<rule id="100041" level="3">
<decoded_as>json</decoded_as>
<field name="event_type">rate_limit_exceeded</field>
<description>API rate limit exceeded</description>
</rule>
</group>
Custom Decoders
<!-- /var/ossec/etc/decoders/local_decoders.xml -->
<decoder name="custom-application">
<program_name>^api-gateway$</program_name>
</decoder>
<decoder name="custom-app-json">
<parent>custom-application</parent>
<plugin_decoder offset="after_parent">JSON_Decoder</plugin_decoder>
</decoder>
<!-- Decoder for application structured logs -->
<decoder name="application-structured">
<program_name>^app-logger$</program_name>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
<!-- Extract fields from API gateway logs -->
<decoder name="api-gateway-access">
<type>syslog</type>
<program_name>^nginx</program_name>
<regex type="pcre2">^(\S+) \S+ \S+ \[(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\] "(\w+) (\S+) HTTP/\d\.\d" (\d{3}) (\d+) "(\S+)" "(.+?)"$</regex>
<order>srcip, timestamp, method, url, status, bytes, referrer, user_agent</order>
</decoder>
Active Response Configuration
<!-- /var/ossec/etc/ossec.conf โ Active Response section -->
<ossec_config>
<command>
<name>firewall-drop</name>
<executable>firewall-drop</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>host-deny</name>
<executable>host-deny</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<command>
<name>custom-block-ip</name>
<executable>custom-block-ip.sh</executable>
<timeout_allowed>yes</timeout_allowed>
</command>
<!-- Block IPs with repeated failed SSH attempts -->
<active-response>
<command>firewall-drop</command>
<location>server</location>
<level>10</level>
<timeout>3600</timeout> <!-- 1 hour block -->
<repeated_offenders>1h,4h,24h,168h</repeated_offenders>
</active-response>
<!-- Block C2 connections immediately -->
<active-response>
<command>custom-block-ip</command>
<location>all</location>
<rules_id>100030</rules_id>
<timeout>0</timeout> <!-- Permanent -->
</active-response>
<!-- Block API abusers -->
<active-response>
<command>custom-block-ip</command>
<location>all</location>
<rules_id>100040</rules_id>
<timeout>7200</timeout> <!-- 2 hour block -->
<repeated_offenders>2h,8h,24h,72h</repeated_offenders>
</active-response>
</ossec_config>
#!/bin/bash
# /var/ossec/active-response/bin/custom-block-ip.sh
# Custom active response script for IP blocking
ACTION=$1
USER=$2
IP=$3
ALERT_ID=$4
RULE_ID=$5
# AWS Security Group integration
AWS_REGION="us-east-1"
SECURITY_GROUP="sg-wazuh-blocklist"
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') $1" >> /var/ossec/logs/active-responses.log
}
case "$ACTION" in
add)
log_message "Blocking IP $IP (Alert: $ALERT_ID, Rule: $RULE_ID)"
# Add to AWS Security Group
aws ec2 authorize-security-group-ingress \
--region "$AWS_REGION" \
--group-id "$SECURITY_GROUP" \
--protocol tcp \
--port 0-65535 \
--cidr "$IP/32" \
--description "Wazuh block: $(date +%s) alert=$ALERT_ID" 2>/dev/null || true
# Also add local iptables rule
iptables -I INPUT -s "$IP" -j DROP
;;
delete)
log_message "Unblocking IP $IP"
# Remove from AWS Security Group
aws ec2 revoke-security-group-ingress \
--region "$AWS_REGION" \
--group-id "$SECURITY_GROUP" \
--protocol tcp \
--port 0-65535 \
--cidr "$IP/32" 2>/dev/null || true
# Remove local iptables rule
iptables -D INPUT -s "$IP" -j DROP
;;
esac
exit 0
Integrations
VirusTotal Integration
<!-- /var/ossec/etc/ossec.conf -->
<ossec_config>
<integration>
<name>virustotal</name>
<api_key>${VIRUSTOTAL_API_KEY}</api_key>
<group>syscheck,pci_dss_11.4</group>
<alert_format>json</alert_format>
<rule_id>100020</rule_id>
</integration>
</ossec_config>
Slack and PagerDuty Alerts
<!-- Slack integration for security team channel -->
<integration>
<name>slack</name>
<hook_url>https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX</hook_url>
<alert_format>json</alert_format>
<level>10</level>
<group>pci_dss,gdpr,nist_800_53</group>
</integration>
<!-- PagerDuty for critical alerts only -->
<integration>
<name>custom</name>
<hook_url>https://events.pagerduty.com/integration/YOUR_INTEGRATION_KEY/enqueue</hook_url>
<alert_format>json</alert_format>
<level>14</level>
<group>attack,c2</group>
</integration>
MITRE ATT&CK Mapping
| Wazuh Rule ID | Detection | MITRE Technique | Tactic |
|---|---|---|---|
| 100001 | Brute force authentication | T1110 โ Brute Force | Credential Access |
| 100010 | Privilege escalation (sudo) | T1548 โ Abuse Elevation Control | Privilege Escalation |
| 100020 | Web shell upload | T1505.003 โ Web Shell | Persistence |
| 100030 | C2 communication | T1071 โ Application Layer Protocol | Command and Control |
| 100040 | API abuse / DoS | T1498 โ Network Denial of Service | Impact |
| 5402 (built-in) | Sudo usage | T1548.003 โ Sudo | Privilege Escalation |
| 5710 (built-in) | SSH brute force | T1110.001 โ Password Guessing | Credential Access |
| 5501 (built-in) | Login failed | T1110 โ Brute Force | Credential Access |
| 554 (built-in) | Web attack (SQLi) | T1190 โ Exploit Public-Facing App | Initial Access |
| 31103 (built-in) | Repeated attacks | T1595.002 โ Vulnerability Scanning | Reconnaissance |
Cloud Security Monitoring
AWS CloudTrail Integration
#!/bin/bash
# wazuh-aws-cloudtrail.sh โ CloudTrail integration setup
BUCKET="company-cloudtrail-logs"
QUEUE_NAME="wazuh-cloudtrail-queue"
echo "[1/3] Creating SQS queue..."
aws sqs create-queue --queue-name "$QUEUE_NAME"
QUEUE_URL=$(aws sqs get-queue-url --queue-name "$QUEUE_NAME" --query 'QueueUrl' --output text)
QUEUE_ARN=$(aws sqs get-queue-attributes --queue-url "$QUEUE_URL" --attribute-names QueueArn --query 'Attributes.QueueArn' --output text)
echo "[2/3] Configuring S3 bucket notifications..."
aws s3api put-bucket-notification-configuration \
--bucket "$BUCKET" \
--notification-configuration '{
"QueueConfigurations": [{
"Id": "wazuh-cloudtrail",
"QueueArn": "'"$QUEUE_ARN"'",
"Events": ["s3:ObjectCreated:*"],
"Filter": {"KeyFilterRules": [{"Name": "prefix", "Value": "AWSLogs/"}]}
}]
}'
echo "[3/3] Configuring Wazuh S3 module..."
cat >> /var/ossec/etc/ossec.conf <<EOF
<wodle name="aws-s3">
<disabled>no</disabled>
<interval>10m</interval>
<run_on_start>yes</run_on_start>
<skip_on_error>yes</skip_on_error>
<bucket type="cloudtrail">
<name>$BUCKET</name>
<aws_profile>default</aws_profile>
</bucket>
</wodle>
EOF
sudo systemctl restart wazuh-manager
echo "CloudTrail integration configured"
File Integrity Monitoring (FIM)
Wazuh's FIM capability monitors critical files and directories for unauthorized changes, generating alerts with before/after comparisons.
<!-- FIM configuration in ossec.conf -->
<syscheck>
<!-- System files -->
<directories check_all="yes" report_changes="yes" realtime="yes">
/etc/passwd,/etc/shadow,/etc/group,/etc/gshadow
</directories>
<directories check_all="yes" report_changes="yes" realtime="yes">
/etc/ssh,/etc/pam.d,/etc/sudoers,/etc/sudoers.d
</directories>
<directories check_all="yes" report_changes="yes" realtime="yes">
/bin,/sbin,/usr/bin,/usr/sbin
</directories>
<!-- Application files -->
<directories check_all="yes" report_changes="yes" realtime="yes">
/var/www,/opt/app,/etc/nginx
</directories>
<!-- Scheduled tasks -->
<directories check_all="yes">
/etc/cron.d,/etc/cron.daily,/etc/cron.hourly,/etc/cron.weekly,/etc/cron.monthly
</directories>
<directories check_all="yes">/var/spool/cron/crontabs</directories>
<!-- Kernel modules -->
<directories check_all="yes">/etc/modprobe.d,/etc/modules-load.d</directories>
<!-- Ignore commonly changing files -->
<ignore>/etc/resolv.conf</ignore>
<ignore>/etc/hdparam</ignore>
<ignore>/etc/network/run</ignore>
<ignore>/etc/mtab</ignore>
<ignore>/etc/prelink.cache</ignore>
<ignore>/etc/resolvconf</ignore>
<ignore>/var/log</ignore>
<ignore>/var/spool/postfix/private</ignore>
<!-- Scan frequency -->
<frequency>43200</frequency> <!-- Every 12 hours -->
<!-- Real-time for critical files is enabled via realtime="yes" above -->
<!-- Who-data audit (Linux) -->
<whodata>
<startup_healthcheck>yes</startup_healthcheck>
</whodata>
</syscheck>
Vulnerability Detection
<!-- Vulnerability detection in ossec.conf (manager-side) -->
<wodle name="vulnerability-detector">
<disabled>no</disabled>
<interval>5m</interval>
<min_full_scan_interval>6h</min_full_scan_interval>
<run_on_start>yes</run_on_start>
<!-- Ubuntu feeds -->
<provider name="canonical">
<enabled>yes</enabled>
<os>focal</os>
<os>jammy</os>
<update_interval>1h</update_interval>
</provider>
<!-- RHEL feeds -->
<provider name="redhat">
<enabled>yes</enabled>
<os>8</os>
<os>9</os>
<update_interval>1h</update_interval>
</provider>
<!-- Amazon Linux -->
<provider name="alas">
<enabled>yes</enabled>
<os>amazon-linux-2</os>
<os>amazon-linux-2022</os>
<update_interval>1h</update_interval>
</provider>
<!-- NVD feed -->
<provider name="nvd">
<enabled>yes</enabled>
<update_interval>1h</update_interval>
</provider>
<!-- Alert severity threshold -->
<!-- Vulnerabilities with CVSS >= 7.0 (HIGH) will generate alerts -->
<!--<severity>Medium</severity>-->
</wodle>
Custom Rules for Compliance
<!-- PCI-DSS compliance rules -->
<group name="pci_dss,">
<!-- PCI 10.2.4 โ Invalid logical access attempts -->
<rule id="200001" level="8">
<if_sid>5710</if_sid>
<description>PCI DSS 10.2.4: Invalid SSH login attempt to $(dstuser)</description>
<group>pci_dss_10.2.4,invalid_login,authentication_failed,</group>
</rule>
<!-- PCI 10.2.5 โ Use of identification and authentication mechanisms -->
<rule id="200002" level="3">
<if_sid>5501</if_sid>
<description>PCI DSS 10.2.5: Successful SSH login by $(dstuser)</description>
<group>pci_dss_10.2.5,authentication_success,</group>
</rule>
<!-- PCI 11.5 โ Unauthorized changes to critical files -->
<rule id="200003" level="11">
<if_sid>550</if_sid>
<field name="file" type="pcre2">/etc/(passwd|shadow|group|ssh|nginx)</field>
<description>PCI DSS 11.5: Critical file modified: $(file)</description>
<group>pci_dss_11.5,syscheck,files_changed,</group>
</rule>
<!-- PCI 11.4 โ Intrusion detection alert -->
<rule id="200004" level="12">
<if_group>attacks</if_group>
<description>PCI DSS 11.4: Intrusion detection alert triggered</description>
<group>pci_dss_11.4,attacks,</group>
</rule>
</group>
<!-- GDPR compliance rules -->
<group name="gdpr,">
<!-- GDPR Article 5 โ Integrity and confidentiality -->
<rule id="200010" level="13">
<if_group>malware</if_group>
<description>GDPR Art.5: Potential data integrity compromise โ malware detected</description>
<group>gdpr_IV_32.2,gdpr_IV_35.7.d,malware,</group>
</rule>
<!-- GDPR Article 32 โ Security of processing -->
<rule id="200011" level="10">
<if_group>rootcheck</if_group>
<description>GDPR Art.32: System anomaly detected โ potential security issue</description>
<group>gdpr_IV_32.2,rootcheck,</group>
</rule>
<!-- GDPR Article 33 โ Data breach notification -->
<rule id="200012" level="14">
<if_matched_sid>200003</if_matched_sid>
<field name="file">/etc/shadow</field>
<description>GDPR Art.33: Potential data breach โ authentication database modified</description>
<group>gdpr_IV_33,gdpr_IV_34.1,pci_dss_10.2.5,pci_dss_11.4,</group>
</rule>
</group>
Wazuh API for Automation
#!/usr/bin/env python3
"""
wazuh_api_client.py โ Python client for Wazuh API automation.
Automates alert retrieval, agent management, and configuration.
"""
import requests
import urllib3
import json
from datetime import datetime, timedelta
from typing import List, Dict, Optional
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
class WazuhAPI:
"""Wazuh API client for security automation."""
def __init__(self, host: str, username: str, password: str, verify_ssl: bool = False):
self.base_url = f"https://{host}:55000"
self.verify = verify_ssl
self.token = self._authenticate(username, password)
def _authenticate(self, username: str, password: str) -> str:
"""Obtain JWT token."""
resp = requests.get(
f"{self.base_url}/security/user/authenticate",
auth=(username, password),
verify=self.verify
)
resp.raise_for_status()
return resp.json()['data']['token']
def _headers(self) -> dict:
return {"Authorization": f"Bearer {self.token}"}
def get_alerts(self, severity: Optional[str] = None,
hours: int = 24, limit: int = 100) -> List[Dict]:
"""Retrieve security alerts from the SIEM."""
query = {
"q": f"timestamp>{self._ago(hours)}",
"sort": "-timestamp",
"limit": limit
}
if severity:
query["q"] += f";rule.level>={self._severity_level(severity)}"
resp = requests.get(
f"{self.base_url}/security/events",
headers=self._headers(),
params=query,
verify=self.verify
)
resp.raise_for_status()
return resp.json()['data']['affected_items']
def get_agents(self, status: Optional[str] = None) -> List[Dict]:
"""List all agents with optional status filter."""
params = {"limit": 1000}
if status:
params["status"] = status
resp = requests.get(
f"{self.base_url}/agents",
headers=self._headers(),
params=params,
verify=self.verify
)
resp.raise_for_status()
return resp.json()['data']['affected_items']
def get_agent_vulnerabilities(self, agent_id: str) -> List[Dict]:
"""Get vulnerability scan results for a specific agent."""
resp = requests.get(
f"{self.base_url}/vulnerability/{agent_id}",
headers=self._headers(),
verify=self.verify
)
resp.raise_for_status()
return resp.json()['data']['affected_items']
def block_ip(self, agent_id: str, ip: str, timeout: int = 3600) -> Dict:
"""Trigger active response to block an IP on an agent."""
resp = requests.put(
f"{self.base_url}/active-response",
headers=self._headers(),
json={
"command": "firewall-drop",
"arguments": [ip, "add", str(timeout)],
"alert": {"data": {"srcip": ip}},
"custom": True
},
params={"agents_list": agent_id},
verify=self.verify
)
resp.raise_for_status()
return resp.json()
def get_fim_events(self, agent_id: str, hours: int = 24) -> List[Dict]:
"""Get file integrity monitoring events for an agent."""
resp = requests.get(
f"{self.base_url}/syscheck/{agent_id}",
headers=self._headers(),
params={
"q": f"date>{self._ago(hours)}",
"sort": "-date",
"limit": 500
},
verify=self.verify
)
resp.raise_for_status()
return resp.json()['data']['affected_items']
@staticmethod
def _ago(hours: int) -> str:
ago = datetime.utcnow() - timedelta(hours=hours)
return ago.strftime("%Y-%m-%dT%H:%M:%SZ")
@staticmethod
def _severity_level(severity: str) -> int:
levels = {"low": 3, "medium": 7, "high": 10, "critical": 13}
return levels.get(severity.lower(), 7)
# Example: Daily security report
if __name__ == "__main__":
import os
wazuh = WazuhAPI(
host=os.environ["WAZUH_HOST"],
username=os.environ["WAZUH_USER"],
password=os.environ["WAZUH_PASS"]
)
# Get critical alerts from last 24 hours
critical_alerts = wazuh.get_alerts(severity="critical", hours=24, limit=50)
print(f"Critical alerts (24h): {len(critical_alerts)}")
# Check agent health
agents = wazuh.get_agents()
offline = [a for a in agents if a['status'] != 'active']
print(f"Total agents: {len(agents)}, Offline: {len(offline)}")
# Get vulnerability summary
total_vulns = 0
for agent in agents[:5]:
vulns = wazuh.get_agent_vulnerabilities(agent['id'])
critical_vulns = [v for v in vulns if v.get('severity') == 'Critical']
total_vulns += len(vulns)
if critical_vulns:
print(f"Agent {agent['name']}: {len(critical_vulns)} critical vulns")
print(f"Total vulnerabilities: {total_vulns}")
Wazuh Agent Deployment at Scale
For large-scale deployments, use configuration management (Ansible/Puppet/Chef) to distribute agents. Deploy agents in groups with centralized agent.conf pushed from the manager for unified policy management. Use the Wazuh API to automate agent registration and group assignment.
Related Topics
- SecOps Overview โ Security frameworks and EDR strategy
- Compliance Frameworks โ SOC 2, ISO 27001, PCI-DSS controls
- Vulnerability Scanning โ Complementary scanning tools