Fail2ban

../_images/fail2ban.png

Das 2004 erschienene Intrusion Prevention System Fail2ban (sinngemäss „Fehlschlag führt zum Bann“) scannt beliebige Log-Dateien mit Filtern auf bestimmte Muster und löst daraufhin eine oder mehrere frei definierbare Actions aus.

Als Aktionen kann Fail2ban beispielsweise:

  • die zugreifende IP-Adresse für einen definierten Zeitraum per iptables, firewalld, pf, ipfw oder nftables sperren

  • Mails inkl. Informationen über den Angreifer senden

  • die verdächtige IP auf Blocklists eintragen

  • dynamische DNS-Updates vornehmen

Filter werden durch anpassbare reguläre Ausdrücke definiert. Fail2ban wird mit einem umfangreichen Filter-Satz für verschiedenste Daemons wie Apache, SSH und andere geliefert.

Filter finden sich unter /etc/fail2ban/filter.d, Aktionen unter /etc/fail2ban/action.d (wo sie auch direkt konfiguriert werden können, z.B. für den Mail-Versand).

Die Kombination aus Filter, Action und einigen weiteren Konfigurationsparametern wird als Jail (Gefängnis) bezeichnet. Für jede Software, die Logdateien erstellt, kann ein Jail erstellt werden.

Fail2ban ist in Python geschrieben und arbeitet seit v0.9 intern mit einer SQLite-Datenbank in /var/lib/fail2ban/fail2ban.sqlite3, um seine Zustandsinformationen zu speichern. Die Datenbank wird auf im Internet hängenden Systemen auch mal 1 GB gross, kann jederzeit gelöscht werden; beim Neustart legt Fail2ban diese einfach wieder an.

Fail2ban besticht durch seine Schlichtheit, seine Unabhängigkeit von den zu prüfenden Diensten und durch die breite Anwendbarkeit.

Die Verzeichnisstruktur:

/etc/fail2ban
├── action.d:
│     Aktionen, die in einem Jail als "action"
│     angegeben werden können
│── filter.d:
│     Definition regulärer Ausdrücke, um Log-
│     Dateien zu untersuchen
└── jail.local:
      Definition des "Gefängnisses" - welche Log-
      Dateien sollen mit welchem Filter untersucht werden,
      welche Aktionen sind anzuwenden, wie lange soll
      geblockt werden usw.
Links

Tipp

In Fail2ban-Konfigurationsdateien Kommentare nie an das Ende von Konfigurationsanweisungen stellen - sonst wird die ganze Zeile deaktiviert. Kommentare gehören immer in eigene Zeilen.

Installation und Konfiguration

Fail2ban muss dort installiert werden, wo die auszuwertenden Log-Dateien liegen. Installiert wird es aus dem EPEL-Repo:

yum -y install fail2ban whois
systemctl enable --now fail2ban

Fail2ban sollte nach dem Start der lokalen Firewall hochfahren, im Beispiel fwb.service. Bei Bedarf ist daher das Unit-File anzupassen:

/etc/systemd/system/multi-user.target.wants/fail2ban.service
After=default.target fwb.service #PartOf=firewalld.service

Der Installer für CentOS ignoriert schon seit Jahren (Stand 2020-11) ein paar Actions, von denen aber zugegeben auch nicht alle auf einem CentOS Sinn machen. Der Vollständigkeit halber:

# get the missing mail action files from GitHub
VER=$(fail2ban-server -V)
cd /etc/fail2ban/action.d
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/bsd-ipfw.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/cloudflare.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/hostsdeny.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/ipfilter.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/ipfw.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/mail-buffered.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/mail-whois-lines.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/mail-whois.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/mail.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/osx-afctl.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/osx-ipfw.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/pf.conf
wget https://raw.githubusercontent.com/fail2ban/fail2ban/$VER/config/action.d/shorewall.conf

Ob Fail2ban arbeitet und was es macht, lässt sich per tail -f /var/log/messages /var/log/fail2ban.log überprüfen - in der Regel genügt ein LogLevel ab NOTICE aufwärts. Von besonderem Interesse sind die Meldungen fail2ban.actions ... NOTICE  [sshd] Ban 10.80.32.156 und ... Unban 10.80.32.156 in /var/log/fail2ban.log.

Deinstallation

systemctl stop fail2ban
systemctl disable fail2ban
yum -y remove fail2ban

Danach:

rm -rf /etc/fail2ban
rm -f  /etc/logrotate.d/fail2ban
rm -rf /etc/selinux/targeted/active/modules/100/fail2ban
rm -rf /run/fail2ban
rm -f  /usr/bin/fail2ban*
rm -rf /usr/lib/python2.7/site-packages/fail2ban*
rm -f  /usr/lib/systemd/system/fail2ban.service
rm -f  /usr/lib/tmpfiles.d/fail2ban.conf
rm -rf /usr/share/doc/fail2ban*
rm -f  /usr/share/man/man1/fail2ban*
rm -rf /var/lib/fail2ban
rm -f  /var/log/fail2ban\*

Fail2ban konfigurieren

Vorgehensweise

  1. Optional: Filter entwickeln und testen

  2. Optional: Action entwickeln und testen

  3. Jail konfigurieren - Filter, Actions und Rahmenbedingungen zusammenführen

Filter entwickeln

In den regulären Ausdrücken kann <HOST> verwendet werden, wenn man die anfragende IP-Adresse matchen möchte - diese Variable ist daher auch in jedem Ausdruck Pflicht.

Reguläre Ausdrücke können so getestet werden:

fail2ban-regex -v /var/log/httpd/access_log 'myregex'

Im Beispiel soll ein Filter entwickelt werden, der eine übermässige Nutzung des Apache httpd verhindert. Erkennbar wird dies in /var/log/httpd/access*log-Dateien durch eine massive Flutung von GET-Requests von der gleichen IP. Am Ende möchten wir also einfach gegen die Anzahl aller Requests in einem bestimmten Zeitraum matchen.

/etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf
# Fail2Ban filter to massive web requests for any ressources on Apache servers
#
# if bantime is low, it effectively slows down a ``ab -n 5000 http://ip-of-my-host/``


[INCLUDES]

# overwrite with apache-common.local if _apache_error_client is incorrect.
before = apache-common.conf


[Definition]

failregex = ^<HOST>

datepattern = ^[^\[]*\[({DATE})
              {^LN-BEG}


# DEV Notes:
#
# example log lines:
# 10.80.32.156 - - [27/Nov/2020:14:10:55 +0100] "GET / HTTP/1.0" 200 252910 9921 www.linuxfabrik.ch -
# 10.80.32.156 - - [27/Nov/2020:14:10:55 +0100] "GET / HTTP/1.0" 200 252910 8355 www.linuxfabrik.ch -
# 10.80.32.156 - - [27/Nov/2020:14:10:55 +0100] "GET / HTTP/1.0" 200 252910 8961 www.linuxfabrik.ch -
# 10.80.32.156 - - [27/Nov/2020:14:10:55 +0100] "GET / HTTP/1.0" 200 252910 8165 www.linuxfabrik.ch -
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

Test gegen das bestehende Logfile.

fail2ban-regex -v /var/log/httpd/access_log /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf

Die zweimalige Angabe der Filter-Datei füttert das Kommando einmal mit dem failregex- und das zweite mal mit dem ignoreregex-Ausdruck (der in unserem Filter nicht vorhanden ist):

fail2ban-regex -v /var/log/httpd/access_log /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf

Es geht noch etwas genauer:

fail2ban-regex /var/log/messages /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf --print-all-matched
fail2ban-regex /var/log/messages /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf --print-all-missed
fail2ban-regex /var/log/messages /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf /etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf --print-all-ignored

Tipp

Wer Variablen in Filtern nutzen möchte, definiert diese in der jail.local und übergibt sie dem filter =-Aufruf, und zwar so:

/etc/fail2ban/jail.local
[linuxfabrik-apache-dos]
filter = linuxfabrik-apache-dos[var1="%(myvar1)s", var2="%(myvar2)s"]
myvar1 = myvalue1
myvar2 = myvalue2

und im Filter:

/etc/fail2ban/filter.d/linuxfabrik-apache-dos.conf
failregex   = ^... <var1> ... <var2>$

Action entwickeln

Im Beispiel soll die IP-Adresse nicht nur gebannt, sondern auch per Rocket.Chat benachrichtigt werden. Es muss also eine neue Action her.

/etc/fail2ban/action.d/linuxfabrik-rocket-chat.conf
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.
#
# 2020120101


[Init]
rockethook =


[Definition]
actionstart =
actionstop =
actioncheck =

actionban = curl --silent --output /dev/null -X POST -H 'Content-Type: application/json' -d '{"text":":fail2ban: banned <ip> for <bantime> seconds after <failures> attempts in jail *<name>* on `<fq-hostname>`\nTo unban use `fail2ban-client set <name> unbanip <ip>`"}' <rockethook>

#actionunban = curl --silent --output /dev/null -X POST -H 'Content-Type: application/json' -d '{"text":":fail2ban: unbanned <ip> from jail *<name>* on `<fq-hostname>`"}' <rockethook>

Diese Aktion benötigt noch ein freigeschaltetes SELinux-Boolean:

setsebool -P nis_enabled on

Jail konfigurieren

In der Jail kommen Action und Filter zusammen und werden mit „bantime“ und anderen Konfigurationsparametern versehen. Hier wird die Jail „linuxfabrik-apache-dos“ definiert, womit der gleichnamige Filter zur Anwendung kommt. IP-Adressen, die 200 und mehr Requests innerhalb von zehn Sekunden gegen den Webserver feuern, werden für 15 Minuten geblockt, und es wird per Rocket.Chat benachrichtigt.

/etc/fail2ban/jail.local
[DEFAULT]
action           = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
                   linuxfabrik-rocket-chat[name=%(__name__)s, rockethook="%(rockethook)s"]
banaction        = iptables-multiport
chain            = INPUT
ignoreip         = 127.0.0.1/8 10.80.32.0/24
logpath          = /var/log/httpd/*access.log
port             = http,https
protocol         = tcp
rockethook       = https://chat.linuxfabrik.io/hooks/692136e4b03b41cc98c2d1aa1fe3bb35

[linuxfabrik-apache-dos]
bantime          = 15m
enabled          = true
findtime         = 10s
maxretry         = 200

Dump der Einstellungen und gleichzeitig Test auf syntaktische Korrektheit:

# fail2ban does not need to be running for this
fail2ban-client -d

Alles aktivieren:

systemctl reload fail2ban

Ignorierte IPs ermitteln

fail2ban-client get $JAIL ignoreip

Gesperrte IPs ermitteln

Mit dem fail2ban-client:

# get list of active jails
fail2ban-client status

# get stats on specific jail and
# "Banned IP list"
fail2ban-client status $JAIL

Alternativ mit iptables (falls damit geblockt wurde):

iptables --list --numeric

Die gesperrten IP-Adressen tauchen bei Verwendung der Action „iptables“ in eigenen iptables-Chains namens „f2b-$JAIL​“ auf, so dass bei bekanntem Namen auch der Aufruf

iptables --list f2b-$JAIL --numeric

genügt. Der Name ergibt sich aus f2b- und angehängtem Jail-Name aus jail.local, im Beispiel oben also f2b-linuxfabrik-apache-dos.

Zählen, wieviele Adressen geblockt wurden (vom Ergebnis noch 3 abziehen):

iptables --list f2b-$JAIL --numeric | wc --lines

IP-Adresse manuell freigeben

Um die durch Fail2ban geblockte IP-Adresse 10.26.6.74 zu entsperren, entfernt man sie aus der passenden Jail:

fail2ban-client set $JAIL unbanip 10.26.6.74

Troubleshooting

Filter enabled, IP taucht auch in der iptables-Liste als geblockt auf, aber Zugriff auf z.B. die Webseite ist immer noch möglich?

Greift ein Client beispielsweise auf meine-ip:9999 zu, wird er geblockt. Ruft er danach meine-ip:443 auf, darf er meine Webseite besuchen. Warum? Hat mich einen Tag gekostet, und wie immer sehr logisch: Fail2ban läuft hier auf einem System, welches direkt am Internet hängt und eingehenden Netzwerk NATtet, z.B. auf einen Webserver. Damit wird der Netzverkehr in der PREROUTING-Chain behandelt und an den Webserver durchgeroutet, durchläuft also nicht die Fail2ban-Chains. Ein Testzugriff auf meine-ip:9998 zeigt, dass alles ausserhalb der NAT-Regeln wirklich geblockt wird.

fail2ban-client: ERROR NOK: (‚database disk image is malformed‘)

SQLite-Datenbank in /var/lib/fail2ban/fail2ban.sqlite3 löschen und Fail2ban neu starten.

ERROR: No failure-id group in …

Fehler im Filter: im regulären Ausdruck fehlt die <HOST>-Angabe.

Built on 2022-06-03