List based permanent bans with fail2ban

Today I post something about the nice little tool fail2ban. As you probably know, fail2ban can be used to block those annoying brute force attacks against your servers. Other than the also popular and useful tool DenyHosts it allows the protection of other services than SSH as well (e.g. HTML login pages served by Apache). The working mechanism also differs from that of DenyHosts, as fail2ban uses iptables instead of the BSD style hosts.deny file to block annoying brute forcers. Installation is quite simple, on Debian for example, just install it through apt and you’re good to go even with the default config.

One thing that I was missing, was the option to ban IPs forever. You can basically do this by setting bantime to a negative value, but as soon as you reload your iptables rules (e.g. by restarting the fail2ban service or the whole system) the entries for the permanently banned IPs are gone.
To overcome this issue, I did some minor changes to the actions fail2ban executes on start-up and on banning.

IMPORTANT: I strongly advise you, to be careful while playing around with automated banning tools, especially if you can’t reach your server physically. Make sure, that you have something useful set in the ignoreip option under the [DEFAULT] jail (your current IP address) to not accidentally lock you out of the system (really nasty with permanent banning active…)

  1. First, check the banaction currently used (you need that, to modify the correct actionfile afterwards)
    /etc/fail2ban/jail.local

    
    #
    # ACTIONS
    #
    ...
    banaction = iptables-multiport
    ...
    
  2. Open up the corresponding actionfile and modify according to the sample below (changes are under the # Persistent banning of IPs comment)
    /etc/fail2ban/action.d/iptables-multiport.conf

    
    ...
    actionstart = iptables -N fail2ban-<name>
                  iptables -A fail2ban-<name> -j RETURN
                  iptables -I INPUT -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
                  # Persistent banning of IPs
                  cat /etc/fail2ban/ip.blacklist | while read IP; do iptables -I fail2ban-<name> 1 -s $IP -j DROP; done
    ...
    actionban = iptables -I fail2ban-<name> 1 -s <ip> -j DROP
                # Persistent banning of IPs
                echo '<ip>' >> /etc/fail2ban/ip.blacklist
    ...
    
  3. Your blacklist should look something like this (one IP per line, of course you can add IPs manually)
    /etc/fail2ban/ip.blacklist

    
    ...
    10.0.0.242
    192.168.1.39
    ...
    
  4. Restart fail2ban to make the changes active

Now, what happens is that each time fail2ban starts, it loops through your ip.blacklist and blocks the IPs in there. If fail2ban blocks a new IP, it will automatically append it to the blacklist.

Links
http://www.fail2ban.org
http://www.fail2ban.org/wiki/index.php/Whitelist
http://denyhosts.sourceforge.net

Update
The following config adds some nice features that were missing in the example above:

  • No duplicate iptables rules (@Lin: might be interesting for you)
  • Jail specific blocking rules (similar to Dr. Tyrell’s and samuelE’s suggestions in the comments)
  • Reporting offender IPs to badips.com

/etc/fail2ban/action.d/iptables-multiport.conf:


# Fail2Ban configuration file
#
# Author: Cyril Jaquier
# Modified by Yaroslav Halchenko for multiport banning and Lukas Camenzind for persistent banning 
#
#
[Definition]
# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = iptables -N fail2ban-<name>
              iptables -A fail2ban-<name> -j RETURN
              iptables -I INPUT -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
              # Load local list of offenders
              if [ -f /etc/fail2ban/ip.blacklist ]; then cat /etc/fail2ban/ip.blacklist | grep -e <name>$ | cut -d "," -s -f 1 | while read IP; do iptables -I fail2ban-<name> 1 -s $IP -j DROP; done; fi
# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop = iptables -D INPUT -p <protocol> -m multiport --dports <port> -j fail2ban-<name>
             iptables -F fail2ban-<name>
             iptables -X fail2ban-<name>
# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck = iptables -n -L INPUT | grep -q fail2ban-<name>
# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionban = if ! iptables -C fail2ban-<name> -s <ip> -j DROP; then iptables -I fail2ban-<name> 1 -s <ip> -j DROP; fi
            # Add offenders to local blacklist, if not already there
            if ! grep -Fxq '<ip>,<name>' /etc/fail2ban/ip.blacklist; then echo '<ip>,<name>' >> /etc/fail2ban/ip.blacklist; fi
            # Report offenders to badips.com
            wget -q -O /dev/null www.badips.com/add/<name>/<ip>
# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    <ip>  IP address
#          <failures>  number of failures
#          <time>  unix timestamp of the ban time
# Values:  CMD
#
actionunban = iptables -D fail2ban-<name> -s <ip> -j DROP
              # Disabled clearing out entry from ip.blacklist (somehow happens after each stop of fail2ban)
              # sed --in-place '/<ip>,<name>/d' /etc/fail2ban/ip.blacklist
[Init]
# Defaut name of the chain
#
name = default
# Option:  port
# Notes.:  specifies port to monitor
# Values:  [ NUM | STRING ]  Default:
#
port = ssh
# Option:  protocol
# Notes.:  internally used by config reader for interpolations.
# Values:  [ tcp | udp | icmp | all ] Default: tcp
# 

35 thoughts on “List based permanent bans with fail2ban

  1. I made a change:
    cat /etc/fail2ban/ip.blacklist- | while read IP; do iptables -I fail2ban- 1 -s $IP -j DROP; done

    echo ” >> /etc/fail2ban/ip.blacklist-

    the basic idea is to save exactly the same list that has been blocked.

    1. Some of the comment has been removed, it should read as follows:


      cat /etc/fail2ban/ip.blacklist- | while read IP; do iptables -I fail2ban- 1 -s $IP -j DROP; done

      echo '' >> /etc/fail2ban/ip.blacklist-

      This creates seperate ip.blacklist files for each row you’re blocking (ssh, apache, etc.).

      1. And it’s been removed again! Even in code tags! Okay, one more time.


        cat /etc/fail2ban/ip.blacklist-<name> | while read IP; do iptables -I fail2ban-<name> 1 -s $IP -j DROP; done

        echo '<ip>' >> /etc/fail2ban/ip.blacklist-<name>

        If it doesn’t work this time I give up.

  2. If only now if we could do the same with those pesky spam referrers !

    Thanks for this post – just what I am looking for. Now I can reboot my server without fiddling !

    Ciao

  3. Thanks for this, the restart problem has been troubling me for a while.
    What would make it even better would be a mechanism to honour the ban-times originally set. I set a pretty high ban-time anyway, but I do think the principle of ban-time is important so your blacklist does not keep growing indefinitely, as this will eventually make iptables become a resource-hog.
    Any thoughts on how to do that?

  4. Hi Robin

    Thanks for your comment. To achieve something that honors the ban-time, you need to put a bit more logic inside the “action” part (e.g. grepping and removing the banned ip in “actionunban”). Also, you need to record the time the ban took place (see field time in iptables-multiport.conf) in order to avoid “dead” banned IPs in your blacklist in case you restart fail2ban… But that would be nice to have indeed. Probably, I will try to figure out something.

    Cheers
    Looke

  5. Alteration of original, to keep one banlist file, and be able to know which filter:

    actionstart = …
    cat /etc/fail2ban/ip.blacklist | while read line; do IP=`echo $line | cut -d’ ‘ -f1`; iptables -I fail2ban- 1 -s $IP -j DROP; done

    actionban = …
    echo ‘ ‘ >> /etc/fail2ban/ip.blacklist

  6. damn, second try to avoid html interpret…

    actionstart = …
      cat /etc/fail2ban/ip.blacklist | while read line; do IP=`echo $line | cut -d’ ‘ -f1`; iptables -I fail2ban-<name> 1 -s $IP -j DROP; done

    actionban = …
      echo ‘<ip> <name>’ >> /etc/fail2ban/ip.blacklist

  7. This was a nice post.

    My requirement is slightly different. I want the IP to be released after specified interval from the ban list. Once released, if an attack comes from the same IP, it bans and releases after the interval again. This loop continues for say 3 / 5 times and after 5 times, the IP is banned permanently.

    This can be done with a custom script but does fail2ban has any inbuilt functionality / configuration to achieve this without external scripting?

    Thanks all in advance,
    Naidu

  8. Hello,

    I implemented the Persistent banning of IPs
    and it works fine but it has an issue.

    It duplicates the iptables rules when an offender ip is saved
    again in /etc/fail2ban/ip.blacklist by fail2ban

    Is there a way to fix it?

    Thanks and Best Regards,

    Lin

  9. Thanks for the great post.
    Do you have any idea how long this list can get without slowing the system too much down?
    Cheers

  10. actionban = iptables -I fail2ban- 1 -s -j DROP
    # Persistent banning of IPs
    echo ‘ ‘ > > /etc/fail2ban/ip.blacklist

    So any banned address is banned permanently ??

    Thanks !

      1. Hola, realice un cambio a las propuestas anteriores, probé y si agregaba permanentemente pero lo metia en todas las cadenas… con esta modificación lo ingresa en la que originalmente en la cadena que callo por primera vez.

        cat /etc/fail2ban/ip.blacklist | while read line; do IP=`echo $line | cut -d ” ” -f1` NAME=`echo $line | cut -d ” ” -f2`; iptables -I fail2ban-$NAME 1 -s $IP -j DROP; done

        Saludos espero podamos seguir merojando esto entre todos.

        1. actionstart =…..
          cat /etc/fail2ban/ip.blacklist | while read line; do IP=`echo $line | cut -d ” ” -f1` NAME=`echo $line | cut -d ” ” -f2`; iptables -I fail2ban-$NAME 1 -s $IP -j DROP; done

          actionban = …………..
          echo ‘ ‘ >> /etc/fail2ban/ip.blacklist

          logico que se tiene que guardar el nombre y la cadena en el mismo archivo.

          1. jajaja mismo problema que Dr. Tyrell ….

            actionban = …………..
            echo ‘ ‘ >> /etc/fail2ban/ip.blacklist

            ejemplo ip.blacklist

            10.1.2.3 Zimbra-audit
            10.2.2.3 Zimbra-account
            10.3.2.3 -Zimbra-recipient

            Saludos

  11. For future googlers, while echoing ip top the ip.blacklist, single quotes wrapped around ip didn’t work for me. I had to remove single quotes and just leave

  12. Hello All,

    Great article.
    Does anyone have a clean current copy of this config?
    I copy & pasted and tried to clean up the above but can’t seem to get it to load the list and for that matter maintain the list.

    Thanks!

  13. Thanks you very much for this really good article and the example conf file. Do you such configuration file for iptables.conf?

    Thanks in advance.

  14. I doubt this will help anyone, but if there are any anal retentive sorts out there like me, this little snippet of code will get all your banned IPs lined up in numerical order so they are sorted both in the ip.blacklist and in the output of `iptables -L -n’

    Note: most of this is from the author of this post. Mine is just the “sort” line and the three lines following:

    actionban = if ! iptables -C fail2ban- -s -j DROP
    then
    iptables -I fail2ban- 1 -s -j DROP
    fi
    # Add offenders to local blacklist, if not already there
    if ! grep -Fxq ‘,’ /etc/fail2ban/ip.blacklist
    then
    echo ‘,’ >> /etc/fail2ban/ip.blacklist
    sort -n -r -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 /etc/fail2ban/ip.blacklist > /etc/fail2ban/temp.txt \
    && mv /etc/fail2ban/temp.txt /etc/fail2ban/ip.blacklist \
    && chmod 640 /etc/fail2ban/ip.blacklist \
    && chown root.root /etc/fail2ban/ip.blacklist

    fi
    # Report offenders to badips.com
    wget -q -O /dev/null http://www.badips.com/add//

  15. Follow up note. The reason they are sorted in reverse in the ip.blacklist file is because of the way they are inserted into the iptables line-up. By putting them in reverse order in blacklist.ip, they will be in normal order when running ‘iptables -L -n’

  16. can you explain this please?
    # Disabled clearing out entry from ip.blacklist (somehow happens after each stop of fail2ban)
    # sed --in-place '/,/d' /etc/fail2ban/ip.blacklist

    I see it’s there but commented out. is it an ongoing issue?

  17. one more question – is the ail name which follows the comma mandatory for this to work? (e.g., “123.45.67.890,ssh”) I tried a couple ips on a line by themself as you mentioned at the top of the article but had no effect until I added “,ssh” after them. Is there a way to make this optional?

    Also, are comments allowed? Would be nice but I’m not sure if it would mess something up with the parsing of the blocklist file

  18. Can anyone advise whether it is possible to block entire ip ranges in the above manner? I’d like to add to the ip.blacklist file two Chinese ranges that have been heavily attempting ssh access to servers I manage. So can I add 1.2.3.o/24 to ip.blacklist and have it drop any attempt to access from any ip in that range?

  19. Bueno despues de multiples pruebas la solucion es

    actionstart = iptables -N fail2ban-
    iptables -A fail2ban- -j RETURN
    iptables -I -p -m multiport –dports -j fail2ban-
    cat /etc/fail2ban/ip.blacklist- | while read IP; do iptables -I fail2ban- 1 -s $IP -j DROP; done

    actionban = iptables -I fail2ban- 1 -s -j DROP
    echo ” >> /etc/fail2ban/ip.blacklist-

  20. For anyone who has the problem where the “ip.blacklist” file is not created: be sure to check you’re editing the correct action file.

  21. I know it’s been a couple of years since the last comment, but I’m going to give it a shot. This could be a HUGE life saver for me if I could get this to work.

    Is there a way to just put in the iptables-allports.conf file a line like the following:

    iptables -I f2b-default -1 -s 1.0.0.0/8 -j DROP

    I don’t want to permanently block every ip that gets tagged as my iptables would have six million lines in there. But. Being able to block a set of CIDR’s would eliminate 90% of my traffic.

    This would allow the other small time kiddies to get blocked for a day and go away as most of them don’t return but once a month.

    Thank you for the article. You’ve given me some great ideas.

Leave a Reply to Ian Cancel 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.