This post contains some random things from my tests with the Wazuh platform. The way the project describes itself is:

The Open Source Security Platform

Unified XDR and SIEM protection for endpoints and cloud workloads.

– https://wazuh.com/

I installed Wazuh on top of Elastic stack. There some alternative installation methods that you can see here. Under the hood Wazuh also uses OSSEC. I had used OSSEC before so I had some idea how the things work and it was quite easy to grasp how Wazuh uses different components of OSSEC.

With my installation method Wazuh’s UI is built on top of Kibana which is part of the Elastic stack. Using Kibana is quite intuitive and there is a nice default alert dashboard that allows to start basic usage very quickly. The below screenshot is from the default security dashboard.

security dashboard

Below the dashboard you can scroll the actual alert events.

Agents and Groups

Wazuh has an agent component that is installed to the hosts you want to monitor. Agents can be organized to different groups and groups can have group specific configurations.

Wazuh UI has a simple form to deploy new agents. You just provide some relevant information and it will give you commands to deploy agents on the host.

Agent deployment Agent deployment

You can change agents’ group after the deployment without re-deploying the agent. This allows easily to make and fine tune logical structures based on different configuration needs.

It would also be really easy to automate this with Ansible or something so mass deployments should be quite easy.

File Integrity Monitoring (FIM)

Wazuh provides ability to monitor files’ integrity on the monitored hosts. You can read more about this here.

Malware hash detection

The idea for this is from Wazuh’s own documentation that can be found here. I won’t repeat everything shown in that document, but will show some minor things I had to modify to make this work and some thigs that I had to figure out.

I downloaded the VirusShare list (mentioned in documentation) from here. Then I used the script from the documentation, but ensured that each line is lowercase since the downloaded content was uppercase and Wazuh’s checksums are lowercase.

with open('VirusShare_0-449_MR4n6_Hash_Sets_RAW_2022_Q4.txt', 'r') as istr:
    with open('malware-hashes', 'w') as ostr:
        for i, line in enumerate(istr):
            if i > 5:
                line = line.rstrip('\n')
                line += ':'
                print(line.lower(), file=ostr)

The example rule in the documentation uses entry <list field="md5" lookup="match_key">etc/lists/malware-hashes</list> and I had a bit hard time understanding where the md5 field comes from. Events from syscheck module contains field syscheck.md5_after, but not md5. Same thing with the field file as an event only had field syscheck.path. Finally I found the answer from this document which explains how FIM fields are mapped to those fields.

To test this rule I added the eicar file’s checksum to the list: echo -n '<md5sum of eicar>:' >> malware-hashes (remember to restart wazuh manager)

Then I downloaded the file to a location that was monitored by wazuh-agent: cd /usr/sbin/ && sudo wget https://secure.eicar.org/eicar.com.txt && rm eicar.com.txt.

Finally, I increased the rule level as I really want to notice if malware is found somewhere. Otherwise the rule I use is the same as in the documentation.

<group name="local,malware,">
  <!-- https://wazuh.com/blog/detecting-and-responding-to-malicious-files-using-cdb-lists-and-active-response/ -->
  <rule id="100002" level="12">
    <if_sid>554</if_sid>
    <list field="md5" lookup="match_key">etc/lists/malware-hashes</list>
    <description>A file - $(file) - in the malware blacklist was added to the system.</description>
  </rule>
  <rule id="100003" level="12">
    <if_sid>100002</if_sid>
    <field name="file" type="pcre2">(?i)[c-z]:</field>
    <description>A file - $(file) - in the malware blacklist was added to the system.</description>
  </rule>
</group>

Example alert

Monitoring Pfsense with Wazuh

Forwarding Pfsense logs to Wazuh

In Wazuh server’s rsyslog configuration I added the following:

  • /etc/rsyslog.d/30-pfsense.conf:
if $fromhost-ip startswith 'PFSENSE IP HERE' then /var/log/pfsense.log
& ~

Then I configured Pfsense to send logs to Wazuh server’s Rsyslog listener. This can be done directly from Pfsense’s Web GUI. Wazuh includes some decoders and alerts for Pfsense by default, so there’s no need to do anything to catch some basic things. For example, I have Snort configured in Pfsense and Wazuh has some working alert rules against Snort logs.

Detect successfull login events to Pfsense Web UI

Here is a custom decoder to parse loging events from Pfsense logs:

<decoder name="pfcustom_ui">
  <program_name>^php-fpm</program_name>
</decoder>

<decoder name="pfcustom_ui">
  <parent>pfcustom_ui</parent>
  <regex>/index.php: Successful login for user '(\.+)' from: (\d+.\d+.\d+.\d+) \(Local Database\)</regex>
  <order>pfsense.gui_login.username,pfsense.gui_login.srcip</order>
</decoder>

Then the rule:

 <!-- Pfsense creates two log lines for one login so ignore for 5 seconds -->
  <rule id="100037" level="12" ignore="5">
    <field name="pfsense.gui_login.username">\.+</field>
    <description>Pfsense UI login for user $(pfsense.gui_login.username) from $(pfsense.gui_login.srcip)</description>
    <group>authentication_success,</group>
  </rule>

I have the rule level quite high (12) as only logons I’m expecting are from myself and I have Slack integration that alerts from events equal or higher to 12.

Detect new MAC addresses doing DHCP discovery

My Pfsense firewall is acting as a DHCP server and I have configured it to log DHCP events. I created the following decoder to decode DHCP discovery events:

<decoder name="pfcustom">
  <program_name>^dhcpd</program_name>
</decoder>

<decoder name="pfcustom">
  <parent>pfcustom</parent>
  <regex>DHCPDISCOVER from (\S+) via (\.+)</regex>
  <order>pfsense.dhcp.requester_mac,pfsense.dhcp.server_interface</order>
</decoder>

<decoder name="pfcustom">
  <parent>pfcustom</parent>
  <regex>DHCPDISCOVER from (\S+) \((\.+)\) via (\.+)</regex>
  <order>pfsense.dhcp.requester_mac,pfsense.dhcp.requester_hostname,pfsense.dhcp.server_interface</order>
</decoder>

Then I created a new list file /var/ossec/etc/lists/known-macs with syntaz like this:

"11:22:33:44:55:66": "some device"
"11:22:33:44:55:77": "other device"

Finally, I created some custom rules to detect new MAC addresses in DHCP discovery:

  <rule id="100035" level="1">
    <field name="pfsense.dhcp.requester_mac">\.+</field>
    <description>DCHP discovery detected</description>
  </rule>

<rule id="100036" level="12" ignore="60">
    <if_sid>100035</if_sid>
    <list field="pfsense.dhcp.requester_mac" lookup="not_match_key">etc/lists/known-macs</list>
    <description>New MAC address detected in DHCP discovery</description>
</rule>

Custom Wireguard decoder and rules

I use Wireguard with my Pfsense firewall and the Wireguard package is a bit annoying when it comes to logging as it doesn’t do any.

I wanted to have some visibility with the Wireguard, but without logs I had to build something to monitor the traffic. There is wg show command that shows some peer information, but at least the version my Pfsense has, doesn’t show peers’ public source address. I decided to do a bit weird tcpdump apparoach to capture source IP’s from Wireguard traffic. Below is a script that runs tcpdump for a 15 seconds, filters with Wireguard port, and parses unique IP addresses from the capture.

#!/bin/bash

export PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/root/bin
# Set correct IP here
SERVER_PEER="IP of the 'server peer'"
IPS=$(timeout 15 tcpdump -n -i igb0 port 51820 2>&1 | grep -E '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' -o | grep -v $SERVER_PEER|sort -u)
for i in  $(echo $IPS)
do
    logger -t wireguard -s "Wireguard traffic from src ip $i"
done

I use a cron job to run this script periodically. My Pfsense sends its system logs to a rsyslog server that has Wazuh agent installed and the agent collects those Pfsense logs. I just needed to add a custom decoder to parse these events:

<decoder name="wireguard">
    <program_name>^wireguard</program_name>
</decoder>

<decoder name="wireguard">
  <parent>wireguard</parent>
  <regex>Wireguard traffic from src ip (\d+.\d+.\d+.\d+)</regex>
  <order>srcip</order>
</decoder>

Then I added some rule to alert from Wireguard traffic:

<group name="wireguard,">
<rule id="100015" level="5">
    <program_name>wireguard</program_name>
    <description>Wireguard traffic from $(srcip)</description>
</rule>
</group>

This is far from perfect, or even a bit stupid, as the capture is not running constantly, so there are gaps in visibility, and it will capture non-valid Wireguard traffic if someone is just knocking the port (script doesn’t even specify UDP/TCP filter). It will also include Wireguard connections that are valid, so it will create false positives. I tried to create a condition based on the source’s geo location, but I was not able to make that work yet. It’s (maybe) better than nothing, but I would like to figure out something better in future. It still works as an example for a custom decoder and rule.

Active Responses

Wazuh allows configuring active responses for specific events or events above specific rule level. In practive active response is a script or executable that is executed by the Wazuh agent. There some default responses and you can also make your own. You can read more active responses here.

Isolate Linux endpoint

This is a simple example of how to perform isolation with a Linux host. It is far from bullet proof, but could potentially prevent some malware from spreading or slow down an attacker.

  • Add script to Linux endpoint(s) /var/ossec/active-response/bin/isolate.sh:
#!/bin/bash

# The script needs to allow connection to your dns server
# if you are using DNS in wazuh-agent configuration

WAZUH_SERVER="<WAZUH IP>"


if [[ "$1" = "restore" ]]
then
    iptables-restore < /root/.orig-iptables-rules
    chattr -i /root/.orig-iptables-rules
else
    iptables-save > /root/.orig-iptables-rules
    chattr +i /root/.orig-iptables-rules
    iptables -I OUTPUT -j REJECT
    iptables -I INPUT -j REJECT
    iptables -I INPUT -m conntrack --ctstate ESTABLISHED,RELATED -s "$WAZUH_SERVER" -j ACCEPT
    iptables -I OUTPUT -p tcp -d "$WAZUH_SERVER" --dport 443 -j ACCEPT
    iptables -I OUTPUT -p tcp -d "$WAZUH_SERVER" --dport 1514 -j ACCEPT
    iptables -I OUTPUT -p tcp -d "$WAZUH_SERVER" --dport 1515 -j ACCEPT
    iptables -I OUTPUT -p tcp -d "$WAZUH_SERVER" --dport 55000 -j ACCEPT
    # Logging the call
    echo "`date` $0 $1 $2 $3 $4 $5" >> ${PWD}/logs/active-responses.log
fi
  • chmod 755 /var/ossec/active-response/bin/isolate.sh

  • Configure Wazuh server /var/ossec/etc/ossec.conf (inside <ossec_config/>:

  <command>
    <name>linux-isolate</name>
    <executable>isolate.sh</executable>
    <timeout_allowed>yes</timeout_allowed>
  </command>

  <active-response>
    <command>linux-isolate</command>
    <location>local</location>
    <level>12</level>
    <timeout>600</timeout>
  </active-response>

This configures linux-isolate active response and it’s called when an alert with the level 12 or above is created.

  • Run systemctl restart wazuh-manager on the Wazuh server.

To test the configuration I will download the Eicar file which will create a level 12 alert with my current configuration. After the download I execute ping 8.8.8.8 to see how quickly connections are dropped.

active-response

It’s possible to verify AR executions from endpoint’s /var/ossec/logs/active-responses.log file. One line in the log looks like this:

la 6.5.2023 21.53.19 +0300 active-response/bin/isolate.sh

To release a host from isolation I need to run /var/ossec/active-response/bin/isolate.sh restore on the host.

Retrieve snort pcap files when triggered by an IDS alert event

I would have wanted to enrich IDS alert with device information from /var/ossec/etc/lists/known-macs list, but I did not find sufficient way to do that with wazuh. I decided to do an active response script that creates HTML reports with device and some public IP information when an alert hits.

To Pfsensen host I created a script /usr/local/bin/get_snort_logs.py:

#!/usr/local/bin/python3.11
import os
import sys
import glob
import datetime

INTERFACE = sys.argv[1]

listing = glob.glob(f'/var/log/snort/snort_{INTERFACE}*')
today = datetime.datetime.now().strftime('%Y-%m-%d')
for parent in listing:
    for i in os.listdir(parent):
        ts = os.path.getmtime(f'{parent}/{i}')
        mod_date = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d')
        if "snort.log" in i and os.stat(f'{parent}/{i}').st_size > 0 and today == mod_date:
            print(f'{parent}/{i}')

This searches for all snort pcap logs and prints out the ones that where modified at the same date as today. I did not create any custom Wazuh alerts as it has some built in rules for Snort.

Next I added the below script to /var/ossec/active-response/bin/get-snort-logs on Wazuh host. The script does the following:

  • Runs the created command get_snort_logs.py on the Pfsense host and uses SCP to retrieve pcap logst that were returned by the get_snort_logs.py command.
  • Outputs alert’s traffic.
  • Checks if any MAC address associated with a private IP is in /var/ossec/etc/lists/known-macs and outputs known devices.
  • Checks country etc. information for public IPs from ipinfo.io

The final result is HTML file per retrieved pcap log file. Result files are stored in /opt/snort_pcap_logs/. The script uses tcpdump, jq ,and tshark which needs to be installed.

Update <SET ME> parts to script and create PFSENSE_SSH* material. Ensure that wazuh user has access (e.g. chown -R wazuh:wazuh /usr/local/etc/pfsense_snort). Also change igb2 on line 15 to match the interface where snort is monitoring for. This is a current limitation with this setup that it doesn’t cover all interfaces, but it could easily be changed. This was just enough for my current use-case.

#!/bin/bash

PFSENSE_USER="<SET ME>"
PFSENSE_IP="<SET ME>"
# https://ipinfo.io/
IP_INFO_API_KEY="<SET ME>"
PFSENSE_SSH_KEY="/usr/local/etc/pfsense_snort/pfsense_id_rsa"
PFSENSE_SSH_KNOWN_HOSTS="/usr/local/etc/pfsense_snort/known_hosts"
DIR="/opt/snort_pcap_logs/$(date '+%Y-%m-%d')"
# Create directory for today and cd into it
mkdir -p $DIR
cd $DIR

# Get snort logs with modification time of today
result=$(ssh -o UserKnownHostsFile=$PFSENSE_SSH_KNOWN_HOSTS -i $PFSENSE_SSH_KEY $PFSENSE_USER@$PFSENSE_IP '/usr/local/bin/get_snort_logs.py igb2')

# Copy snort logs to local host
for r in $result
do
    scp -o UserKnownHostsFile=$PFSENSE_SSH_KNOWN_HOSTS -i $PFSENSE_SSH_KEY $PFSENSE_USER@$PFSENSE_IP:$r .
    echo "<h1>Traffic from file $(basename $r)</h1>" > $(basename $r).html
    echo '<pre><code>' >> $(basename $r).html
    tcpdump -n -r $(basename $r) >> $(basename $r).html
    echo '</code></pre>' >> $(basename $r).html

    # Find known devices from known_macs list
    SEEN_MACS=()
    SEEN_IPS=()
    echo '<h1>Known devices</h1>' >> $(basename $r).html
    echo '<ul>' >> $(basename $r).html
    for i in $(tshark -r snort.log.1693613945 -T fields -e eth.src -e ip.src -e eth.dst -e ip.dst -E separator=,)
    do
        SRC_MAC=$(echo $i|awk -F ',' '{print $1}')
        SRC_IP=$(echo $i|awk -F ',' '{print $2}')
        DST_MAC=$(echo $i|awk -F ',' '{print $3}')
        DST_IP=$(echo $i|awk -F ',' '{print $4}')
        if [[ ! " ${SEEN_MACS[*]} " =~ " ${SRC_MAC} " && $(echo $SRC_IP | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)' | wc -l) -gt 0 ]]; then
            SEEN_MACS+=("$SRC_MAC")
            mac_known=$(grep $SRC_MAC /var/ossec/etc/lists/known-macs)
            if [[ $(echo $mac_known|wc -l) -gt 0 ]]
            then
                echo "<li>$(echo $mac_known|awk -F '\":' '{print $2}'|tr -d '\"') ($SRC_IP)</li>" >> $(basename $r).html
            fi
        fi
        if [[ ! " ${SEEN_MACS[*]} " =~ " ${DST_MAC} " && $(echo $DST_IP | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)' | wc -l) -gt 0 ]]; then
            SEEN_MACS+=("$DST_MAC")
            mac_known=$(grep $DST_MAC /var/ossec/etc/lists/known-macs)
            if [[ $(echo $mac_known|wc -l) -gt 0 ]]
            then
                echo "<li>$(echo $mac_known|awk -F '\":' '{print $2}'|tr -d '\"') ($DST_IP)</li>" >> $(basename $r).html
            fi
        fi
    done
    echo '</ul>' >> $(basename $r).html
    for i in $(tshark -r snort.log.1693613945 -T fields -e eth.src -e ip.src -e eth.dst -e ip.dst -E separator=,)
    do
        SRC_MAC=$(echo $i|awk -F ',' '{print $1}')
        SRC_IP=$(echo $i|awk -F ',' '{print $2}')
        DST_MAC=$(echo $i|awk -F ',' '{print $3}')
        DST_IP=$(echo $i|awk -F ',' '{print $4}')
        # Get IP information for public IP addresses
        if [[ ! " ${SEEN_IPS[*]} " =~ " ${SRC_IP} " && $(echo $SRC_IP | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)' | wc -l) -eq 0 ]];then
            SEEN_IPS+=("$SRC_IP")
            ip_info=$(curl -H "Authorization: Bearer $IP_INFO_API_KEY" -H "Accept: application/json" https://ipinfo.io/$SRC_IP)
            echo "<h1>IP information for $SRC_IP</h1>" >> $(basename $r).html
            echo '<ul>' >> $(basename $r).html
            echo "<li>Org: $(echo $ip_info|jq '."org"')</li>" >> $(basename $r).html
            echo "<li>Country: $(echo $ip_info|jq '."country"')</li>" >> $(basename $r).html
            echo "<li>City: $(echo $ip_info|jq '."city"')</li>" >> $(basename $r).html
            echo "<li>Region: $(echo $ip_info|jq '."region"')</li>" >> $(basename $r).html
            echo "<li>Loc: $(echo $ip_info|jq '."loc"')</li>" >> $(basename $r).html
            echo "<li>Hostname: $(echo $ip_info|jq '."hostname"')</li>" >> $(basename $r).html
            echo '</ul>' >> $(basename $r).html
        fi
        if [[ ! " ${SEEN_IPS[*]} " =~ " ${DST_IP} " && $(echo $DST_IP | grep -E '^(192\.168|10\.|172\.1[6789]\.|172\.2[0-9]\.|172\.3[01]\.)' | wc -l) -eq 0 ]]; then
            SEEN_IPS+=("$DST_IP")
            ip_info=$(curl -H "Authorization: Bearer $IP_INFO_API_KEY" -H "Accept: application/json" https://ipinfo.io/$DST_IP)
            echo "<h1>IP information for $DST_IP</h1>" >> $(basename $r).html
            echo '<ul>' >> $(basename $r).html
            echo "<li>Org: $(echo $ip_info|jq '."org"')</li>" >> $(basename $r).html
            echo "<li>Country: $(echo $ip_info|jq '."country"')</li>" >> $(basename $r).html
            echo "<li>City: $(echo $ip_info|jq '."city"')</li>" >> $(basename $r).html
            echo "<li>Region: $(echo $ip_info|jq '."region"')</li>" >> $(basename $r).html
            echo "<li>Loc: $(echo $ip_info|jq '."loc"')</li>" >> $(basename $r).html
            echo "<li>Hostname: $(echo $ip_info|jq '."hostname"')</li>" >> $(basename $r).html
            echo '</ul>' >> $(basename $r).html
        fi
    done
done

# Logging the call
echo "`date` $0 $1 $2 $3 $4 $5" >> /var/ossec/logs/active-responses.log

Finally add active response configuration to /var/ossec/etc/ossec.conf on the Wazuh host:


  <command>
    <name>get-snort-logs</name>
    <executable>get-snort-logs</executable>
    <timeout_allowed>yes</timeout_allowed>
  </command>

  <active-response>
    <disabled>no</disabled>
    <command>get-snort-logs</command>
    <location>server</location>
    <rules_group>ids</rules_group>
    <timeout>600</timeout>
  </active-response>

One way to test that wazuh can run the script is to su -s /bin/bash wazuh, then cd /var/ossec/ and ./active-response/bin/get-snort-logs.

I also created a simple nginx config to view HTML reports:

server {
  listen        80 default_server;

  location / {
    autoindex on;
    root  /opt/snort_pcap_logs/;
  }
}

Just ensure that Wazuh and Nginx has access to /opt/snort_pcap_logs/ and that Nginx can read the reports. This may work out of the box, but depending on your umask configuration you may need to modify the script to chmod correct permissions for files and directories.

When the script runs the end result should look something like this:

 ls -la /opt/snort_pcap_logs/2023-09-02/
total 84
drwxr-xr-x 2 wazuh www-data  4096 Sep  2 18:10 .
drwxr-xr-x 3 wazuh www-data  4096 Sep  2 13:55 ..
-rw------- 1 wazuh www-data  4572 Sep  2 18:09 snort.log.1693613945
-rw-r--r-- 1 wazuh www-data  5020 Sep  2 18:09 snort.log.1693613945.html
-rw------- 1 wazuh www-data 31188 Sep  2 18:09 snort.log.1693614211
-rw-r--r-- 1 wazuh www-data 26070 Sep  2 18:09 snort.log.1693614211.html

Pfsense and Suricata

There doesn’t seem to be built-in decoder for suricata alerts if you are using it instead of snort. The only difference with logging from snort is the program name in syslog messages. Easy solution for this is to copy built-in snort decoder and just replace “snort” with “suricata”.

<decoder name="suricata">
  <program_name>^suricata</program_name>
</decoder>

<decoder name="suricata">
  <type>ids</type>
  <prematch>^[**] [\d+:\d+:\d+] </prematch>
</decoder>

<decoder name="suricata2">
   <parent>suricata</parent>
   <type>ids</type>
   <prematch>^[**] |^[\d+:\d+:\d+] </prematch>
   <regex>^[**] [(\d+:\d+:\d+)] \.+ (\S+)\p*\d* -> </regex>
   <regex>(\S+)|^[(\d+:\d+:\d+)] \.+ </regex>
   <regex>(\S+)\p*\d* -> (\S+)</regex>
   <order>id,srcip,dstip</order>
   <fts>name,id,srcip,dstip</fts>
</decoder>

After this, same IDS rules that are generated from Snort alerts should work with Suricata events.

Wazuh also has examples here for parsing Suricata’s JSON formatted eve logs.

Monitoring USB events with USBGuard

I run USBGuard on Linux workstations and wanted to get alerts when an unauthorized device is connected.

I created the following decoder for USBGuard logs:

<decoder name="usbguard">
  <program_name>^usbguard-daemon</program_name>
</decoder>

<decoder name="usbguard">
  <parent>usbguard</parent>
  <regex>result='(\w+)' device.system_name='(\.+)' target.new='(\w+)' device.rule='(\.+)'</regex>
  <order>usbguard.result,usbguard.device.system_name,usbguard.target.new,usbguard.device.rule</order>
</decoder>

Then the following rule:

<group name="ubs,usbguard,">
<rule id="100031" level="10">
    <field name="usbguard.target.new">block</field>
    <description>Unauthorized USB device detected</description>
</rule>
</group>

Wazuh already has 0375-usb_rules.xml which has events for attached USB devices, so for me it was enough to create a rule to alert when unauthorized device is attached.

Wazuh also has a blog bost about monitoring USB devices with Windows if you are interested in that: https://wazuh.com/blog/monitoring-usb-drives-in-windows-using-wazuh/.

Other tips & tricks

MISP integration

I didn’t have MISP instance to test this and was too lazy to setup one for now, but here’s an interesting blog post that explains how to enrich Wazuh alerts with MISP data: https://opensecure.medium.com/wazuh-and-misp-integration-242dfa2f2e19.

If ip in subnet

I had a bit hard time accepting that Wazuh didn’t have any pre-defined rule syntax to test if an IP is part of a subnet, but it seems to be the case. There were many cases where I wanted to test if some IP was part of a specific subnet and the best way to accomplish this seems to be CBD lists.

As you can find from the linked documentation, Wazuh’s CBD lists has a special dot notation to match subnets. Creating lists for each network I wanted to use in my rules allowed to <if ip in subnet> like checks.

Reducing noise

There are (at least) two ways to reduce alerts from noisy rules. First is overwrite the whole rule and set its rule level to zero. This will silence all alerts from the rule.

  <rule id="80730" level="0" overwrite="yes">
    <if_sid>80700</if_sid>
    <field name="audit.type">AVC</field>
    <description>Auditd: SELinux permission check.</description>
    <group>audit_selinux,gdpr_IV_30.1.g,gdpr_IV_35.7.d,hipaa_164.312.b,nist_800_53_AU.6,pci_dss_10.6.1,tsc_CC7.2,tsc_CC7.3,</group>
  </rule>

You just copy the whole rule and important thing to set is level="0" overwrite="yes".

It’s also possible to silence alerts based on some conditions. Just create a new rule with the relevant if_sid. Then specify correct conditions and set rule level to zero. This way you can reduce some noise without disabling the whole rule.

<rule id="100010" level="0">
    <if_sid>87702</if_sid>
    <list field="srcip" lookup="address_match_key">etc/lists/trusted_src_ips</list>
    <description>Rule muted when the srcip is in the trusted source IP list</description>
</rule>

I also had a one clear false positive from the rootcheck module:

Trojaned version of file '/usr/bin/diff' detected. Signature used: 'bash|^/bin/sh|file\.h|proc\.h|/dev/[^n]|^/bin/.*sh' (Generic).

I found a fix for this from Github.

-diff        !bash|^/bin/sh|file\.h|proc\.h|/dev/[^n]|^/bin/.*sh!
+diff        !bash|^/bin/sh|file\.h|proc\.h|/dev/[^nf]|^/bin/.*sh!