Create a suricata rules file using fail2ban

In my last posts I’ve shown a central syslog which feeds fail2ban, suricata as an intrusion prevention system (IPS) and here is the final piece which feeds suricata with the results of fail2ban by creating a .rules file for suricata-update.

You can make a very very simple, automatic list just with a little bit Bash. Create a /etc/fail2ban/action.d/firewall.conf with the following content:

[Definition]

actionstart = 

actionstop =

actioncheck =

actionban = /var/www/f2b/bin/ban.sh <ip>

actionunban = /var/www/f2b/bin/unban.sh <ip>

I picked /var/www/f2b/bin as path but you might pick another path for those Bash-Scripts. Also you might not need to use two Bash-Scripts: You could also use one and make the first parameter decide if ban or unban. Anyway:

ban.sh

#!/bin/bash
#
# Author: Jean Bruenn <himself@jeanbruenn.info>
# See: https://blog.jeanbruenn.info/2023/11/19/create-a-suricata-rules-list-using-fail2ban/

# the first parameter given by fail2ban (IP)
IP="$1"
# where to store the ruleset?
RULESFILE="/var/www/f2b/htdocs/fail2ban.rules"
# what message should be in suricata logs?
MSG="BRUTE-FORCE detected by fail2ban"

# I am using TSTAMP and a CNT to create the SID
# you might have a better way to do this :^)
TSTAMP=$(date +%s)
CNT=$(wc -l $RULESFILE | cut -d' ' -f1);
SID=$(($CNT + $TSTAMP))

if ! grep -q "$IP" $RULESFILE; then
  # If you want to drop all traffic you would use something like this
  RULE="drop ip $IP any -> \$HOME_NET any (msg:\"$MSG\"; sid:$SID;)"
  # If you just want to drop traffic to SSH-Ports
  # RULE="drop ip $IP any -> \$HOME_NET \$SSH_PORTS (msg:\"$MSG\"; sid:$SID;)"
  #
  # the following should also work
  # RULE="drop ssh $IP any -> \$HOME_NET \$SSH_PORTS (msg:\"$MSG\"; sid:$SID;)"

  echo $RULE >> $RULESFILE
  # you might need to change this :-)
  chown www-data:www-data $RULESFILE

  # whenever a rule is added we need to change the MD5 accordingly:
  SUM=$(md5sum $RULESFILE | cut -d' ' -f1);
  echo $SUM > $RULESFILE.md5
fi

This ban script simply takes an IP as argument, checks if that IP is in the RULESFILE and if not adds the IP with a suricata rule. In the end, because those rules are appended to the rules file it modifies the .md5 file.

unban.sh

#!/bin/bash
#
# Author: Jean Bruenn <himself@jeanbruenn.info>
# See: https://blog.jeanbruenn.info/2023/11/19/create-a-suricata-rules-list-using-fail2ban/

# the first parameter given by fail2ban (IP)
IP="$1"
# where is the ruleset?
RULESFILE="/var/www/f2b/htdocs/fail2ban.rules"

if grep -q "$IP" $RULESFILE; then
  sed -i '/'$IP'/d' $RULESFILE
  
  # whenever a rule is added we need to change the MD5 accordingly:
  SUM=$(md5sum $RULESFILE | cut -d' ' -f1);
  echo $SUM > $RULESFILE.md5
fi

This is a very naive implementation; it just checks if the IP is in the rulefile and if yes fires sed to remove that line with the IP. Then it updates the md5sum. You may want to add some extra safety to $IP. You might replace sed with a grep or awk solution.

You can test both by issuing ./ban.sh 1.2.3.4 and ./unban.sh 1.2.3.4. Remember to make both files executable using e.g. chmod a+x. Make this .rules file available via NGINX or a webserver you like somewhere. Add some ssl certificate for it and let suricata-update fetch it:

root@fw2:~# suricata-update add-source
19/11/2023 -- 20:17:21 - <Info> -- Loading /etc/suricata/update.yaml
19/11/2023 -- 20:17:21 - <Info> -- Using data-directory /var/lib/suricata.
19/11/2023 -- 20:17:21 - <Info> -- Using Suricata configuration /etc/suricata/suricata.yaml
19/11/2023 -- 20:17:21 - <Info> -- Using /etc/suricata/rules for Suricata provided rules.
19/11/2023 -- 20:17:21 - <Info> -- Found Suricata version 6.0.10 at /usr/bin/suricata.
URL: https://f2b.example.com/fail2ban.rules

Now run suricata-update and you should see:

19/11/2023 -- 20:17:38 - <Info> -- Fetching https://f2b.example.com/fail2ban.rules.
 100% - 18344/18344                   
19/11/2023 -- 20:17:38 - <Info> -- Done.

Verify if it works by checking the suricata logs:

root@fw2:/var/log/suricata# awk '/Drop/{$1="";$2="";$3="";$4="";sub(/\[.*/,"");gsub(/^[ \t]+|[ \t]+$/,"");sub(/group.*/,"");print}' fast.log | sort | uniq -c | sort -hr
   2402 ET DROP Dshield Block Listed Source 
    541 BRUTE-FORCE detected by fail2ban
    269 ET SCAN Potential SSH Scan
    134 ET COMPROMISED Known Compromised or Hostile Host Traffic 
     66 ET SCAN LibSSH Based Frequent SSH Connections Likely BruteForce Attack
     14 ET DROP Spamhaus DROP Listed Traffic Inbound 

This example only makes use of Fail2ban’s SSH log analysis. Fail2ban is able to do (detect) much more. You can create different fail2ban rules for suricata for example using multiple different actions (suricata-ssh.conf suricata-smtp.conf in /etc/fail2ban/action.d for example). However, the above should give you a starting point.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.