Server inklusive Docker-Container mit Fail2Ban absichern

Hacking Linux

Niemand möchte, das sein Server gehackt wird. Eine 100%-Lösung dagegen wird es kaum geben, außer den Server offline zu nehmen. Alles was du tun kannst, ist dich um möglichst viel Sicherheit bemühen. Und da setzen wir in diesem Blogbeitrag an.

Fail2Ban ist ein kleiner, netter Dienst, der periodisch Logfiles mit definierten Filtern nach suspekten Einträgen durchsucht und dann mittels iptables die verursachende IP für einen bestimmten Zeitraum sperrt. Simple, oder?

Zuerst installieren wir uns fail2ban und werfen einen Blick auf die Konfigurationsdateien:

# apt install fail2ban
# cd /etc/fail2ban/
# cp jail.conf jail.local
# ls -la
total 100
drwxr-xr-x  6 root root  4096 Nov  2 14:41 .
drwxr-xr-x 90 root root  4096 Oct 27 07:09 ..
drwxr-xr-x  2 root root  4096 Nov  2 14:14 action.d
-rw-r--r--  1 root root  2816 Nov 23  2020 fail2ban.conf
drwxr-xr-x  2 root root  4096 Jul 12 06:52 fail2ban.d
drwxr-xr-x  3 root root  4096 Nov  2 14:13 filter.d
-rw-r--r--  1 root root 24996 Nov 23  2020 jail.conf
drwxr-xr-x  2 root root  4096 Oct 15 07:37 jail.d
-rw-r--r--  1 root root 25178 Nov  2 14:41 jail.local
-rw-r--r--  1 root root   645 Nov 23  2020 paths-arch.conf
-rw-r--r--  1 root root  2827 Nov 23  2020 paths-common.conf
-rw-r--r--  1 root root   573 Nov 23  2020 paths-debian.conf
-rw-r--r--  1 root root   738 Nov 23  2020 paths-opensuse.conf

# ls filter.d/
3proxy.conf                apache-shellshock.conf  directadmin.conf    gitlab.conf             monit.conf            oracleims.conf          roundcube-auth.conf   squid.conf         wuftpd.conf
apache-auth.conf           assp.conf               domino-smtp.conf    grafana.conf            murmur.conf           pam-generic.conf        screensharingd.conf   squirrelmail.conf  xinetd-fail.conf
apache-badbots.conf        asterisk.conf           dovecot.conf        groupoffice.conf        mysqld-auth.conf      perdition.conf          selinux-common.conf   sshd.conf          znc-adminlog.conf
apache-botsearch.conf      bitwarden.conf          dropbear.conf       gssftpd.conf            nagios.conf           phpmyadmin-syslog.conf  selinux-ssh.conf      stunnel.conf       zoneminder.conf
apache-common.conf         botsearch-common.conf   drupal-auth.conf    guacamole.conf          named-refused.conf    php-url-fopen.conf      sendmail-auth.conf    suhosin.conf
apache-fakegooglebot.conf  centreon.conf           ejabberd-auth.conf  haproxy-http-auth.conf  nginx-botsearch.conf  portsentry.conf         sendmail-reject.conf  tine20.conf
apache-modsecurity.conf    common.conf             exim-common.conf    horde.conf              nginx-http-auth.conf  postfix.conf            sieve.conf            traefik-auth.conf
apache-nohome.conf         counter-strike.conf     exim.conf           ignorecommands          nginx-limit-req.conf  proftpd.conf            slapd.conf            uwimap-auth.conf
apache-noscript.conf       courier-auth.conf       exim-spam.conf      kerio.conf              nsd.conf              pure-ftpd.conf          softethervpn.conf     vsftpd.conf
apache-overflows.conf      courier-smtp.conf       freeswitch.conf     lighttpd-auth.conf      openhab.conf          qmail.conf              sogo-auth.conf        webmin-auth.conf
apache-pass.conf           cyrus-imap.conf         froxlor-auth.conf   mongodb-auth.conf       openwebmail.conf      recidive.conf           solid-pop3d.conf      wplogin.conf

# ls action.d/
abuseipdb.conf      firewallcmd-allports.conf      iptables-allports.conf               mail.conf                nsupdate.conf                      sendmail-whois-ipmatches.conf
apf.conf            firewallcmd-common.conf        iptables-common.conf                 mail-whois-common.conf   osx-afctl.conf                     sendmail-whois-lines.conf
badips.conf         firewallcmd-ipset.conf         iptables.conf                        mail-whois.conf          osx-ipfw.conf                      sendmail-whois-matches.conf
badips.py           firewallcmd-multiport.conf     iptables-ipset-proto4.conf           mail-whois-lines.conf    pf.conf                            shorewall.conf
blocklist_de.conf   firewallcmd-new.conf           iptables-ipset-proto6-allports.conf  mynetwatchman.conf       route.conf                         shorewall-ipset-proto6.conf
bsd-ipfw.conf       firewallcmd-rich-logging.conf  iptables-ipset-proto6.conf           netscaler.conf           sendmail-buffered.conf             smtp.py
cloudflare.conf     firewallcmd-rich-rules.conf    iptables-multiport.conf              nftables-allports.conf   sendmail-common.conf               symbiosis-blacklist-allports.conf
complain.conf       helpers-common.conf            iptables-multiport-log.conf          nftables.conf            sendmail.conf                      ufw.conf
docker-action.conf  hostsdeny.conf                 iptables-new.conf                    nftables-multiport.conf  sendmail-geoip-lines.conf          xarf-login-attack.conf
dshield.conf        ipfilter.conf                  iptables-xt_recent-echo.conf         nginx-block-map.conf     sendmail-whois.conf
dummy.conf          ipfw.conf                      mail-buffered.conf                   npf.conf                 sendmail-whois-ipjailmatches.conf

# ls jail.d/
defaults-debian.conf

# cat jail.d/defaults-debian.conf
[sshd]
enabled = true

Wie oben bereits erwähnt, passieren grundsätzlich 3 Dinge:

  1. Aufgabe definieren: Welches Logfile soll nach was durchsucht werden und dann soll was passieren (wird in jail.conf bzw. besser in jail.local (lokale Kopie) konfiguriert)
  2. mit Filtern nach bestimmten Ausdrücken suchen (Filter werden in einer Datei unter /etc/fail2ban/filter.d/ definiert)
  3. bestimmte Aktionen ausführen (Aktionen werden in einer Datei unter /etc/fail2ban/action.d/ definiert)

Wie man sieht, gibt es bereits etliche vorkonfigurierte „Jails“ für Apache, MySQL, Asterisk, sendmail, WordPress, SSH und und und. Unter Debian ist die Überwachung des Dienstes SSH bereits aktiviert (siehe Datei /etc/fail2ban/jail.d/defaults-debian.conf). Schön, dann schauen wir uns mal die Wirkungsweise von fail2ban an Hand von SSH näher an.

Beginnen wir mit dem Abschnitt für sshd in der Datei jail.local. Es wird empfohlen, die jail.conf nicht direkt zu bearbeiten, sondern mittels cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local eine Kopie der Datei anzulegen und diese zukünftig zu bearbeiten, denn das System greift beim Vorhandensein dieser Datei selbstständig auf die jail.local zurück (lies dazu auch die Kopfzeilen der jail.conf bzw. dann jail.local):

# nano /etc/jail.local

...
[sshd]

# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode   = normal
port    = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
...

Seit fail2ban 0.9 sind die Pfade zu den Logdateien und Backends nicht mehr direkt in der jail.conf verlinkt, sondern in der Datei /etc/fail2ban/paths-common.conf bzw. als paths-debian.conf (Settings für arch und opensuse sind auch vorhanden) hinterlegt. So wird u.a. der Pfad zur Datei /var/log/auth.log definiert, die alle ssh-Loginversuche protokolliert. Dann schauen wir uns das Logfile für SSH-Anmeldungen unter Debian genauer an:

# tail -n 18 /var/log/auth.log
Nov  3 10:49:02 debian sshd[4158907]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.178.202  user=root
...
Nov  3 10:49:25 debian sshd[4158907]: error: maximum authentication attempts exceeded for root from 192.168.178.202 port 19005 ssh2 [preauth]
Nov  3 10:49:25 debian sshd[4158907]: Disconnecting authenticating user root 192.168.178.202 port 19005: Too many authentication failures [preauth]
Nov  3 10:49:25 debian sshd[4158907]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.178.202  user=root
Nov  3 10:49:29 debian sshd[4159096]: Accepted password for bob from 192.168.178.202 port 19008 ssh2
Nov  3 10:49:29 debian sshd[4159096]: pam_unix(sshd:session): session opened for user bob(uid=1000) by (uid=0)
Nov  3 10:49:29 debian systemd-logind[539]: New session 17842 of user bob.
Nov  3 10:49:29 debian sshd[4159106]: Accepted password for bob from 192.168.178.202 port 19009 ssh2
Nov  3 10:49:29 debian sshd[4159106]: pam_unix(sshd:session): session opened for user bob(uid=1000) by (uid=0)
Nov  3 10:49:29 debian systemd-logind[539]: New session 17843 of user bob.
Nov  3 10:49:41 debian sudo: bob : TTY=pts/2 ; PWD=/home/bob ; USER=root ; COMMAND=/usr/bin/cat /var/log/auth.log
Nov  3 10:49:41 debian sudo: pam_unix(sudo:session): session opened for user root(uid=0) by bob(uid=1000)

Zuerst siehst du ein Login-Versuch per SSH durch Benutzer root, dieser schlägt nach mehreren Versuchen fehl. Anschließend versucht es Benutzer bob, er hat Erfolg. Anschließend versucht bob mit sudo-Rechten die Datei /var/log/auth.log mittels des Programms cat unter /usr/bin/cat auszugeben – auch erfolgreich. Ganz schön viele Infos, oder? Was wir jetzt suchen, sind Einträge über misslungene Login-Versuche und vor allem dessen Anzahl und dessen zeitlichen Zusammenhang. Dies ist wie bereits erwähnt in der Datei sshd.conf vorkonfiguriert.

# cat /etc/fail2ban/filter.d/sshd.conf
# Fail2Ban filter for openssh
#
# If you want to protect OpenSSH from being bruteforced by password
# authentication then get public key authentication working before disabling
# PasswordAuthentication in sshd_config.
#
#
# "Connection from <HOST> port \d+" requires LogLevel VERBOSE in sshd_config
#

[INCLUDES]

# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf

[DEFAULT]

_daemon = sshd

# optional prefix (logged from several ssh versions) like "error: ", "error: PAM: " or "fatal: "
__pref = (?:(?:error|fatal): (?:PAM: )?)?
# optional suffix (logged from several ssh versions) like " [preauth]"
#__suff = (?: port \d+)?(?: \[preauth\])?\s*
__suff = (?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*
__on_port_opt = (?: (?:port \d+|on \S+)){0,2}
# close by authenticating user:
__authng_user = (?: (?:invalid|authenticating) user <F-USER>\S+|.*?</F-USER>)?

# for all possible (also future) forms of "no matching (cipher|mac|MAC|compression method|key exchange method|host key type) found",
# see ssherr.c for all possible SSH_ERR_..._ALG_MATCH errors.
__alg_match = (?:(?:\w+ (?!found\b)){0,2}\w+)

# PAM authentication mechanism, can be overridden, e. g. `filter = sshd[__pam_auth='pam_ldap']`:
__pam_auth = pam_[a-z]+

[Definition]

prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>%(__pref)s<F-CONTENT>.+</F-CONTENT>$

cmnfailre = ^[aA]uthentication (?:failure|error|failed) for <F-USER>.*</F-USER> from <HOST>( via \S+)?%(__suff)s$
            ^User not known to the underlying authentication module for <F-USER>.*</F-USER> from <HOST>%(__suff)s$
            <cmnfailre-failed-pub-<publickey>>
            ^Failed <cmnfailed> for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
            ^<F-USER>ROOT</F-USER> LOGIN REFUSED FROM <HOST>
            ^[iI](?:llegal|nvalid) user <F-USER>.*?</F-USER> from <HOST>%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not listed in AllowUsers%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because listed in DenyUsers%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because not in any group%(__suff)s$
            ^refused connect from \S+ \(<HOST>\)
            ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*3: .*: Auth fail%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because a group is listed in DenyGroups%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> from <HOST> not allowed because none of user's groups are listed in AllowGroups%(__suff)s$
            ^<F-NOFAIL>%(__pam_auth)s\(sshd:auth\):\s+authentication failure;</F-NOFAIL>(?:\s+(?:(?:logname|e?uid|tty)=\S*)){0,4}\s+ruser=<F-ALT_USER>\S*</F-ALT_USER>\s+rhost=<HOST>(?:\s+user=<F-USER>\S*</F-USER>)?%(__suff)s$
            ^maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?%(__suff)s$
            ^User <F-USER>\S+|.*?</F-USER> not allowed because account is locked%(__suff)s
            ^<F-MLFFORGET>Disconnecting</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+</F-USER> <HOST>%(__on_port_opt)s:\s*Change of username or service not allowed:\s*.*\[preauth\]\s*$
            ^Disconnecting: Too many authentication failures(?: for <F-USER>\S+|.*?</F-USER>)?%(__suff)s$
            ^<F-NOFAIL>Received <F-MLFFORGET>disconnect</F-MLFFORGET></F-NOFAIL> from <HOST>%(__on_port_opt)s:\s*11:
            <mdre-<mode>-other>
            ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)

cmnfailed-any = \S+
cmnfailed-ignore = \b(?!publickey)\S+
cmnfailed-invalid = <cmnfailed-ignore>
cmnfailed-nofail = (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+)
cmnfailed = <cmnfailed-<publickey>>

mdre-normal =
# used to differentiate "connection closed" with and without `[preauth]` (fail/nofail cases in ddos mode)
mdre-normal-other = ^<F-NOFAIL><F-MLFFORGET>(Connection closed|Disconnected)</F-MLFFORGET></F-NOFAIL> (?:by|from)%(__authng_user)s <HOST>(?:%(__suff)s|\s*)$

mdre-ddos = ^Did not receive identification string from <HOST>
            ^kex_exchange_identification: (?:[Cc]lient sent invalid protocol identifier|[Cc]onnection closed by remote host)
            ^Bad protocol version identification '.*' from <HOST>
            ^<F-NOFAIL>SSH: Server;Ltype:</F-NOFAIL> (?:Authname|Version|Kex);Remote: <HOST>-\d+;[A-Z]\w+:
            ^Read from socket failed: Connection <F-MLFFORGET>reset</F-MLFFORGET> by peer
# same as mdre-normal-other, but as failure (without <F-NOFAIL>) and [preauth] only:
mdre-ddos-other = ^<F-MLFFORGET>(Connection (?:closed|reset)|Disconnected)</F-MLFFORGET> (?:by|from)%(__authng_user)s <HOST>%(__on_port_opt)s\s+\[preauth\]\s*$

mdre-extra = ^Received <F-MLFFORGET>disconnect</F-MLFFORGET> from <HOST>%(__on_port_opt)s:\s*14: No(?: supported)? authentication methods available
            ^Unable to negotiate with <HOST>%(__on_port_opt)s: no matching <__alg_match> found.
            ^Unable to negotiate a <__alg_match>
            ^no matching <__alg_match> found:
# part of mdre-ddos-other, but user name is supplied (invalid/authenticating) on [preauth] phase only:
mdre-extra-other = ^<F-MLFFORGET>Disconnected</F-MLFFORGET>(?: from)?(?: (?:invalid|authenticating)) user <F-USER>\S+|.*?</F-USER> <HOST>%(__on_port_opt)s \[preauth\]\s*$

mdre-aggressive = %(mdre-ddos)s
                  %(mdre-extra)s
# mdre-extra-other is fully included within mdre-ddos-other:
mdre-aggressive-other = %(mdre-ddos-other)s

# Parameter "publickey": nofail (default), invalid, any, ignore
publickey = nofail
# consider failed publickey for invalid users only:
cmnfailre-failed-pub-invalid = ^Failed publickey for invalid user <F-USER>(?P<cond_user>\S+)|(?:(?! from ).)*?</F-USER> from <HOST>%(__on_port_opt)s(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
# consider failed publickey for valid users too (don't need RE, see cmnfailed):
cmnfailre-failed-pub-any =
# same as invalid, but consider failed publickey for valid users too, just as no failure (helper to get IP and user-name only, see cmnfailed):
cmnfailre-failed-pub-nofail = <cmnfailre-failed-pub-invalid>
# don't consider failed publickey as failures (don't need RE, see cmnfailed):
cmnfailre-failed-pub-ignore =

cfooterre = ^<F-NOFAIL>Connection from</F-NOFAIL> <HOST>

failregex = %(cmnfailre)s
            <mdre-<mode>>
            %(cfooterre)s

# Parameter "mode": normal (default), ddos, extra or aggressive (combines all)
# Usage example (for jail.local):
#   [sshd]
#   mode = extra
#   # or another jail (rewrite filter parameters of jail):
#   [sshd-aggressive]
#   filter = sshd[mode=aggressive]
#
mode = normal

#filter = sshd[mode=aggressive]

ignoreregex =

maxlines = 1

journalmatch = _SYSTEMD_UNIT=sshd.service + _COMM=sshd

# DEV Notes:
#
#   "Failed \S+ for .*? from <HOST>..." failregex uses non-greedy catch-all because
#   it is coming before use of <HOST> which is not hard-anchored at the end as well,
#   and later catch-all's could contain user-provided input, which need to be greedily
#   matched away first.
#
# Author: Cyril Jaquier, Yaroslav Halchenko, Petr Voralek, Daniel Black and Sergey Brester aka sebres
# Rewritten using prefregex (and introduced "mode" parameter) by Serg G. Brester.

Jetzt die Datei /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf im Detail zu erläutern, würde schlichtweg zu weit führen. Gerade bei ssh und dem Umfang der auth.log sowie den vielfältigen Varianten der Verbindungsaufnahme gibt es zu viele Möglichkeiten, um alle im Detail durchzugehen. Zum Glück gibt es da bereits vorgefertigte Filter. Aber ich erläutere dir dies gleich noch etwas einfacher am Beispiel von einer WordPress-Docker-Instanz.

Im Abschnitt [Definitions] findet sich unter anderem im Unterabschnitt cmnfailre die Suche nach dem Ausdruck „authentication failure“. Übersichtlicher schaut das aus, wenn du mit dem Kommando fail2ban-regex speziell Logdateien mit dem passenden Filter abgleichst. Auch hier wird dir der Ausdruck „authentication failure“ ausgespuckt und es finden sich 2 Einträge von root dazu.

# fail2ban-regex /var/log/auth.log /etc/fail2ban/filter.d/sshd.conf

Running tests
=============

Use   failregex filter file : sshd, basedir: /etc/fail2ban
Use         maxlines : 1
Use      datepattern : {^LN-BEG} : Default Detectors
Use         log file : /var/log/auth.log
Use         encoding : UTF-8


Results
=======

Prefregex: 10544 total
|  ^(?P<mlfid>(?:\[\])?\s*(?:<[^.]+\.[^.]+>\s+)?(?:\S+\s+)?(?:kernel:\s?\[ *\d+\.\d+\]:?\s+)?(?:@vserver_\S+\s+)?(?:(?:(?:\[\d+\])?:\s+[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?|[\[\(]?sshd(?:\(\S+\))?[\]\)]?:?(?:\[\d+\])?:?)\s+)?(?:\[ID \d+ \S+\]\s+)?)(?:(?:error|fatal): (?:PAM: )?)?(?P<content>.+)$
`-

Failregex: 20 total
|-  #) [# of hits] regular expression
|   4) [7] ^Failed (?:<F-NOFAIL>publickey</F-NOFAIL>|\S+) for (?P<cond_inv>invalid user )?<F-USER>(?P<cond_user>\S+)|(?(cond_inv)(?:(?! from ).)*?|[^:]+)</F-USER> from <HOST>(?: (?:port \d+|on \S+)){0,2}(?: ssh\d*)?(?(cond_user): |(?:(?:(?! from ).)*)$)
|  14) [2] ^<F-NOFAIL>pam_[a-z]+\(sshd:auth\):\s+authentication failure;</F-NOFAIL>(?:\s+(?:(?:logname|e?uid|tty)=\S*)){0,4}\s+ruser=<F-ALT_USER>\S*</F-ALT_USER>\s+rhost=<HOST>(?:\s+user=<F-USER>\S*</F-USER>)?(?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*$
|  15) [1] ^maximum authentication attempts exceeded for <F-USER>.*</F-USER> from <HOST>(?: (?:port \d+|on \S+)){0,2}(?: ssh\d*)?(?: (?:port \d+|on \S+|\[preauth\])){0,3}\s*$
|  21) [10] ^<F-MLFFORGET><F-MLFGAINED>Accepted \w+</F-MLFGAINED></F-MLFFORGET> for <F-USER>\S+</F-USER> from <HOST>(?:\s|$)
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [10544] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
`-

Lines: 10544 lines, 12 ignored, 8 matched, 10524 missed
[processed in 2.80 sec]

|- Ignored line(s):
|  Nov  1 16:19:05 debian sshd[3492659]: Accepted password for bob from 192.168.178.33 port 56356 ssh2
|  Nov  1 16:19:05 debian sshd[3492714]: Accepted password for bob from 192.168.178.33 port 56358 ssh2
|  Nov  2 08:20:09 debian sshd[3739170]: Accepted password for bob from 192.168.178.202 port 2791 ssh2
|  Nov  2 08:20:10 debian sshd[3739196]: Accepted password for bob from 192.168.178.202 port 2795 ssh2
|  Nov  2 10:46:36 debian sshd[3776281]: Accepted password for bob from 192.168.178.202 port 27977 ssh2
|  Nov  2 10:46:36 debian sshd[3776291]: Accepted password for bob from 192.168.178.202 port 27980 ssh2
|  Nov  3 07:32:23 debian sshd[4072434]: Accepted password for bob from 192.168.178.202 port 17932 ssh2
|  Nov  3 07:32:23 debian sshd[4072456]: Accepted password for bob from 192.168.178.202 port 17939 ssh2
|  Nov  3 10:18:20 debian sshd[4150841]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.178.202  user=root
|  Nov  3 10:49:02 debian sshd[4158907]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.178.202  user=root
|  Nov  3 10:49:29 debian sshd[4159096]: Accepted password for bob from 192.168.178.202 port 19008 ssh2
|  Nov  3 10:49:29 debian sshd[4159106]: Accepted password for bob from 192.168.178.202 port 19009 ssh2
`-
Missed line(s): too many to print.  Use --print-all-missed to print all 10524 lines

Wenn die maximale Anzahl an fehlerhaften Login-Versuchen erreicht wird, kommen die Aktionen zum tragen. Dazu finden sich Standard-Actions in der jail.local, die Einträge in den iptables machen und somit IP-Adressen bannen (sperren) und nach der festgelegten zeit auch wieder entsperren

...
# Default banning action (e.g. iptables, iptables-new,
# iptables-multiport, shorewall, etc) It is used to define
# action_* variables. Can be overridden globally or per
# section within jail.local file
banaction = iptables-multiport
banaction_allports = iptables-allports
...

Dies schaut bei mir z.B. derzeit so aus (Auszug aus der Chain f2b-sshd):

# iptables -L
...
Chain f2b-sshd (1 references)
target     prot opt source               destination
REJECT     all  --  61.177.172.89        anywhere             reject-with icmp-port-unreachable
REJECT     all  --  181.115.146.34       anywhere             reject-with icmp-port-unreachable
REJECT     all  --  clientanalyticscampaigns.com  anywhere             reject-with icmp-port-unreachable
REJECT     all  --  v017051.ppp.asahi-net.or.jp  anywhere             reject-with icmp-port-unreachable
REJECT     all  --  122.194.229.40       anywhere             reject-with icmp-port-unreachable
REJECT     all  --  112.85.42.28         anywhere             reject-with icmp-port-unreachable
REJECT     all  --  122.194.229.54       anywhere             reject-with icmp-port-unreachable
REJECT     all  --  167.71.234.157       anywhere             reject-with icmp-port-unreachable
REJECT     all  --  177-139-137-190.dsl.telesp.net.br  anywhere             reject-with icmp-port-unreachable
REJECT     all  --  106.75.126.6         anywhere             reject-with icmp-port-unreachable
REJECT     all  --  20.71.193.60         anywhere             reject-with icmp-port-unreachable
REJECT     all  --  106.12.80.223        anywhere             reject-with icmp-port-unreachable
...

Dies funktioniert in dieser Form für relativ viele Dienste, die direkt auf dem Host laufen und fertig vorkonfiguriert sind. Bei Docker-Containern verhält es sich deutlich anders. Das fängt schon damit an, das die Log-Dateien der einzelnen Dockerinstanzen nicht im Standardverzeichnis auf dem Host liegen, sondern „irgendwo“ im Dockercontainer. Da eine deiner ersten Docker-Instanzen vermutlich eine WordPress-Installation ist, werde ich dir die Konfiguration daran erläutern. Zuerst gilt es also herauszufinden, wo die auszuwertende Logdatei zu finden ist. Dazu suchst du den passenden Containernamen heraus

# docker ps | grep wordpress
24c76f4215ba   wordpress:latest                  "docker-entrypoint.s…"   3 months ago   Up 3 weeks   80/tcp

Das wird bei dir ähnlich aussehen, das entscheidende ist gleich am Anfang die Container-ID (die ist bei dir garantiert eine andere). Mit docker logs 24c76f4215ba kannst du dir das Logfile auch vom Host aus ansehen.

Wie kommt fail2ban jetzt aber an das Log-File aus dem Docker-Container? Denn das Logfile liegt ja im Container und fail2ban läuft in unserem Fall auf dem Host. Aber auch hier gibt es einen Kniff. Docker legt nämlich die Konfigurations-Dateien, aber auch das Logfile zu jeder einzelnen Docker-Instanz im Verzeichnis /var/lib/docker/container/<CONTAINER-ID>/ ab (Datei CONTAINER-ID-json.log) ab:

# ls -la /var/lib/docker/containers/24c76f4215ba*/
insgesamt 89924
drwx--x---  4 root root     4096 15. Jan 08:58 .
drwx--x--- 30 root root     4096 29. Jan 08:04 ..
-rw-r-----  1 root root 92030280  6. Feb 11:59 24c76f4215ba5b2a680e7dcd27704d01db23af931c6a4251c685744022385336-json.log
drwx------  2 root root     4096 20. Okt 12:33 checkpoints
-rw-------  1 root root     4583 15. Jan 08:58 config.v2.json
-rw-r--r--  1 root root     1974 15. Jan 08:58 hostconfig.json
-rw-r--r--  1 root root       13 15. Jan 08:58 hostname
-rw-r--r--  1 root root      174 15. Jan 08:58 hosts
drwx--x---  2 root root     4096 20. Okt 12:33 mounts
-rw-r--r--  1 root root       38 15. Jan 08:58 resolv.conf
-rw-r--r--  1 root root       71 15. Jan 08:58 resolv.conf.hash

Da wir jetzt den Pfad und den Namen des Logfile (hier unser WordPress-Container) haben, können wir diesen nach ungewöhnlichen Ausdrücken druchsuchen. Mit dem Wissen, das die Administrations-Oberfläche von WordPress standardmäßig über den Link https://URL/wp-login.php aufgerufen wird, ist dies ein erster Ansatz, um nach Anomalien zu suchen.

root@kwellkorn:~# cat /var/lib/docker/containers/24c76f4215ba*/24c76f4215ba*-json.log | grep wp-login.php
...
{"log":"39.116.218.200 - - [04/Feb/2022:12:10:39 +0000] \"POST /wp-login.php HTTP/1.1\" 200 3217 \"https://kwellkorn.de/wp-login.php\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36\"\n","stream":"stdout","time":"2022-02-04T12:10:41.193149376Z"}
...

Dies ist natürlich nur ein ganz kleiner Auszug aus meiner Log-Datei, das entscheidende ist die IP (hier 39.116.218.200), denn die ist es in diesem konkreten Beispiel, die gesperrt werden soll. Wenn du dir diese Zeile mal im Vergleich zu weiteren bei dir aufgelisteten näher ansiehst, kannst du mit ein wenig Wissen zum Thema „Regular Expression“ gezielt nach diesen Zeichenketten filtern, da bestimmte Zeichenketten sich pro Zeile immer wiederholen. Und so erstellen wir uns einen neuen Filter:

# nano /etc/fail2ban/filter.d/wplogin.conf

[Definition]
failregex = {"log":"<HOST> -.*POST.*wp-login.php.*
ignoreregex =

Den soeben erstellten Filter testen wir erst einmal, dafür hat fail2ban auch einen passendes Tool mit etlichen Zusatzfeatures parat:

# fail2ban-regex -v /var/lib/docker/containers/24c76f4215ba*/24c76f4215ba*-json.log /etc/fail2ban/filter.d/wplogin.conf

Running tests
=============

Use   failregex filter file : wplogin, basedir: /etc/fail2ban
Use         log file : /var/lib/docker/containers/24c76f4215ba5b2a680e7dcd27704d01db23af931c6a4251c685744022385336/24c76f4215ba5b2a680e7dcd27704d01db23af931c6a4251c685744022385336-json.log
Use         encoding : UTF-8


Results
=======

Failregex: 5732 total
|-  #) [# of hits] regular expression
|   1) [5732] {"log":"<HOST> -.*POST.*wp-login.php.*
|      167.71.207.186  Wed Oct 20 15:20:56 2021
|      103.56.149.232  Wed Oct 20 15:29:54 2021
|      35.201.240.201  Wed Oct 20 15:40:04 2021
...
|      222.151.46.103  Fri Feb 04 13:09:50 2022
|      222.151.46.103  Fri Feb 04 13:09:54 2022
|      175.206.76.253  Fri Feb 04 13:09:56 2022
|      211.210.217.92  Fri Feb 04 13:09:59 2022
|      221.150.227.75  Fri Feb 04 13:10:02 2022
|      221.150.227.75  Fri Feb 04 13:10:07 2022
|      191.53.195.227  Fri Feb 04 13:10:10 2022
|      74.120.174.135  Fri Feb 04 13:10:15 2022
|      191.14.179.183  Fri Feb 04 13:10:19 2022
|      191.14.179.183  Fri Feb 04 13:10:23 2022
|      112.137.123.5  Fri Feb 04 13:10:25 2022
|      126.62.86.159  Fri Feb 04 13:10:28 2022
|      77.123.121.197  Fri Feb 04 13:10:31 2022
|      177.190.75.145  Fri Feb 04 13:10:35 2022
|      39.116.218.200  Fri Feb 04 13:10:39 2022
...

Okay, der Filter funktioniert und kann zum suchen nach unerwünschten IP-Adressen genutzt werden. Interessant in meinem Beispiel oben, wieviel gescheiterten Login-Versuche alleine in den 2 Minuten am 04. Februar waren, oder? Ein Grund mehr, dem mal einen Riegel vorzuschieben.

Also erstellen wir uns als nächstes einen neuen Jail und editieren dazu die Datei /etc/fail2ban/jail.local bzw. ergänzen ganz am Ende den Eintrag [wplogin]


#nano /etc/fail2ban/jail.local
...

[wplogin]
enabled = true
port = http,https
filter = wplogin
logpath = /var/lib/docker/containers/24c76f4215ba*/24c76f4215ba*-json.log
banaction = wplogin-action
maxretry = 5
findtime = 120
bantime = 86400

Mit enabled = true wird das jail aktiviert. Wir suchen gezielt nach Anfragen auf Port 80 oder 443 (http(s), mittels des Filters wplogin (den haben wir in der wplogin.conf in einem der letzten Schritte erstellt), in der angegebenen WordPress-Logdatei unseres Containers. Bei einen Treffer wird die Action wplogin-action (auch hier kannst du dir das .conf am Ende von wplogin-action sparen) ausgeführt und es müssen noch 5 gescheiterte Loginversuche in 120 Sekunden als Bedingung erfüllt sein. Wenn das alles zusammenkommt, ist die IP für 86400 Sekunden weg vom Fenster 🙂

Als nächstes erstellst du die Datei /etc/fail2ban/action.d/docker-action.conf und legst Standard-Aktionen (generieren von Chains für unsere WordPress-Instanz innerhalb von iptables) fest. Weiterhin was passieren soll, wenn alle Bedingungen erfüllt wurden und die IP gesperrt werden soll, mit welchen Kommandos soll die Sperre aufgehoben werden:

# nano /etc/fail2ban/action.d/wplogin-action.conf

[Definition]
 
actionstart = iptables -N f2b-wplogin
              iptables -A f2b-wplogin -j RETURN
              iptables -I FORWARD -p tcp -m multiport --dports 80 -j f2b-wplogin
 
actionstop = iptables -D FORWARD -p tcp -m multiport --dports 80 -j f2b-wplogin
             iptables -F f2b-wplogin
             iptables -X f2b-wplogin
 
actioncheck = iptables -n -L FORWARD | grep -q 'f2b-wplogin[ \t]'
 
actionban = iptables -I f2b-wplogin 1 -s <ip> -j DROP
 
actionunban = iptables -D f2b-wplogin -s <ip> -j DROP

Jetzt muss du den fail2ban-Dienst nur noch neu starten und sowohl sshd als auch die WordPress-Docker-Instanz sind abgesichert.

# systemctl restart fail2ban
# fail2ban-client reload

Das kannst auch auch leicht überprüfen:

# systemctl status fail2ban.service
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-02-05 08:28:42 CET; 1 day 6h ago
       Docs: man:fail2ban(1)
    Process: 980282 ExecStartPre=/bin/mkdir -p /run/fail2ban (code=exited, status=0/SUCCESS)
   Main PID: 980283 (fail2ban-server)
      Tasks: 7 (limit: 9510)
     Memory: 17.1M
        CPU: 1min 43.730s
     CGroup: /system.slice/fail2ban.service
             └─980283 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

# fail2ban-client status
Status
|- Number of jail:      2
`- Jail list:   sshd, wplogin

# fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 2
|  |- Total failed:     2871
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 81
   |- Total banned:     552
   `- Banned IP list:   211.108.51.122 103.39.222.186 180.167.214.190 163.114.131.64 43.155.77.170 5.101.99.198 167.99.96.114 37.186.117.123 221.181.185.94 112.85.42.88 112.85.42.15 206.189.119.230 82.196.5.221 121.4.92.162 219.145.61.20 43.155.62.136 118.34.14.126 43.155.68.187 106.53.91.250 54.39.235.200 147.182.137.182 112.85.42.73 122.194.229.38 106.75.84.194 157.230.151.241 117.50.174.243 43.154.44.40 43.154.85.208 112.85.42.119 202.65.69.193 112.85.42.227 43.135.160.246 143.198.23.4 104.207.132.131 178.128.248.121 89.175.29.126 38.72.132.227 49.235.123.184 195.19.102.173 121.65.121.149 221.181.185.111 2.82.234.17 112.85.42.128 93.39.225.138 43.154.129.214 222.187.238.58 221.181.185.143 112.85.42.89 190.145.12.233 122.194.229.45 117.122.212.78 49.235.92.187 49.234.190.10 179.43.80.6 113.128.122.98 221.122.119.79 159.65.156.27 49.88.112.60 111.230.201.59 49.232.173.143 85.155.150.76 194.163.147.71 78.159.98.209 143.198.167.99 141.136.47.251 167.86.107.85 157.230.105.246 117.111.1.42 122.194.229.40 43.154.57.141 112.85.42.87 150.95.25.221 47.176.104.74 190.247.83.170 221.181.185.151 82.156.12.198 221.181.185.159 218.188.85.193 35.199.73.100 186.4.222.45 139.155.69.95
# fail2ban-client status wplogin
Status for the jail: wplogin
|- Filter
|  |- Currently failed: 1
|  |- Total failed:     3
|  `- File list:        /var/lib/docker/containers/24c76f4215ba5b2a680e7dcd27704d01db23af931c6a4251c685744022385336/24c76f4215ba5b2a680e7dcd27704d01db23af931c6a4251c685744022385336-json.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     42
   `- Banned IP list:   202.65.69.193

Wie du siehst, sind jetzt schon 2 Dienste mit fail2ban abgesichert, SSH und WordPress (als Docker-Instanz).

Im Grunde ist es immer wieder dasselbe, ich fasse zusammen:

  1. Du erstellst dir in der jail.conf einen neuen jail und definierst die Bedingungen, den Filter, die Action und die Timer
  2. Du suchst das zum Dienst zugehörige Logfile und suchst nach Fehlermeldungen und in diesen Zeilen nach wiederkehrenden Ausdrücken , an Hand du den HOST (also die gesuchte IP-Adresse) herausfiltern kannst
  3. Du definierst eine regular expression auf Grundlage der in Schritt 2 erlangten Informationen
  4. Du definierst eine Action, die dir grundsätzlich die notwendigen Chains in iptables anlegt und anschließend, was passieren sollm wenn ein Client gesperrt werden soll oder dessen Sperre wieder aufgehoben wird.

Schauen wir uns noch ein weiteres Beispiel an. Auf meinem selbst betriebenen DNS-Server (siehe dazu den passenden Blog-Beitrag Adguard Home) kommen seit geraumer Zeit täglich mehrere 10000 DNS-Anfragen täglich z.B. für pizzaseo.com. Dabei handelt es sich um gezielte, automatisierte DDoS-DNS-Angriffe auf meinen Server. Da ich meinen DNS-Server allgemein verfügbar halten wollte, bin ich dazu übergegangen, die betroffenen Hosts seperat in Adguard Home zu sperren, irgendwann wurde mir das aber zu aufwändig und ich wollte den Prozess automatisieren. Also habe ich ein entsprechendes Jail in Fail2Ban erstellt und überlasse dieses Kampffeld jetzt auch diesem Service.

Aber schauen wir uns das doch einmal im Detail an. Das passende Logfile von Adguard Home liegt bei mir unter /var/adguard/work/data/querylog.json.

# cat /var/adguardhome/work/data/querylog.json | grep pizzaseo.com
...
{"T":"2022-02-10T17:12:46.444293902Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"f0+BgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"84.63.6.237","Elapsed":24245}
{"T":"2022-02-10T17:12:46.444367573Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"f0+BgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"84.63.6.237","Elapsed":779779}
{"T":"2022-02-10T17:12:46.444372101Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"f0+BgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"84.63.6.237","Elapsed":810637}
{"T":"2022-02-10T17:12:58.980888349Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"QoGBgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"93.218.214.246","Elapsed":129275}
{"T":"2022-02-10T17:12:58.981049294Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"QoGBgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"93.218.214.246","Elapsed":54183}
{"T":"2022-02-10T17:12:58.981137181Z","QH":"pizzaseo.com","QT":"RRSIG","QC":"IN","CP":"","Answer":"QoGBgwABAAAAAQAACHBpenphc2VvA2NvbQAALgABCHBpenphc2VvA2NvbQAABgABAAAACgBUGWZha2UtZm9yLW5lZ2F0aXZlLWNhY2hpbmcHYWRndWFyZANjb20ACmhvc3RtYXN0ZXIIcGl6emFzZW8DY29tAAABiJQAAAcIAAADhAAJOoAAAVGA","Result":{"IsFiltered":true,"Reason":3,"Rules":[{"Text":"||pizzaseo.com^$important"}]},"IP":"93.218.214.246","Elapsed":123243}
...

Was nämlich diese DNS-Anfragen alle gemeinsam haben, ist der Umstand, das diese allesamt für pizzaseo.com gestellt werden. Da das Logfile immer dieselbe Struktur hat, lässt sich mit Regular Expression sehr gut nach den IPs suchen, von denen diese Attacken ausgehen. Die Menge an Suchanfragen nach pizzaseo.com ist nicht zu übersehen, glaubt mir.

Und das, obwohl ich bereits etliche Hosts einzeln über die Client-Konfiguration blockiere und Anfragen auf pizzaseo.com grundsätzlich nicht beantworte. Aber das löst ja nicht das eigentliche Problem, denn die DNS-Anfrage wird ja trotzdem gestellt und belastet den Server. Also bauen wir uns einen Filter (hier kann ich regex101.com empfehlen).

# nano /etc/fail2ban/filter.d/adguardhome.conf

[Definition]
failregex = "pizzaseo.com".*"IP":"<HOST>".*
ignoreregex =

Ich suche in meinem Fall (bei dir kann das Logfile anders aussehen, da in meinem Fall die Domain pizzaseo.com im Adguard-DNS-Filter ohnehin schon geblockt wird und dieser Vorgang auch im Logfile so dokumentiert wird) also nach dem Ausdruck pizzaseo.com, gefolgt von einer Wildcard (.*), gefolgt von dem Ausdruck „IP“:“ und dann der eigentlichen IP (<HOST>). Der Rest der Zeile filter ich wieder mittels einer Wildcard (.*) weg. Was also bleibt ist die IP-Adresse des Angreifers (wer stellt sonst DNS-Anfragen für pizzaseo.com :-)) und das testen wir auch gleich:

# fail2ban-regex -v /var/adguardhome/work/data/querylog.json /etc/fail2ban/filter.d/adguardhome.conf

Running tests
=============

Use   failregex filter file : adguardhome, basedir: /etc/fail2ban
Use         log file : /var/adguardhome/work/data/querylog.json
Use         encoding : UTF-8


Results
=======

Failregex: 890 total
|-  #) [# of hits] regular expression
|   1) [890] "pizzaseo.com".*"IP":"<HOST>".*
|      94.134.196.37  Sun Feb 06 23:09:00 2022
|      94.134.196.37  Sun Feb 06 23:09:00 2022
|      94.134.196.37  Sun Feb 06 23:09:00 2022
|      94.134.196.37  Sun Feb 06 23:09:00 2022
|      94.134.196.37  Sun Feb 06 23:09:00 2022
...
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
|      84.63.6.237  Thu Feb 10 17:13:54 2022
`-

Ignoreregex: 0 total

Date template hits:
|- [# of hits] date format
|  [282004] ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T|  ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
|  [0] {^LN-BEG}ExYear(?P<_sep>[-/.])Month(?P=_sep)Day(?:T|  ?)24hour:Minute:Second(?:[.,]Microseconds)?(?:\s*Zone offset)?
|  [0] {^LN-BEG}(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] {^LN-BEG}(?:DAY )?MON Day ExYear %k:Minute:Second(?:\.Microseconds)?
|  [0] {^LN-BEG}Day(?P<_sep>[-/])Month(?P=_sep)(?:ExYear|ExYear2) %k:Minute:Second
|  [0] {^LN-BEG}Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour:Minute:Second(?:\.Microseconds)?(?: Zone offset)?
|  [0] {^LN-BEG}Month/Day/ExYear:24hour:Minute:Second
|  [0] {^LN-BEG}Month-Day-ExYear %k:Minute:Second(?:\.Microseconds)?
|  [0] {^LN-BEG}Epoch
|  [0] {^LN-BEG}ExYear2ExMonthExDay  ?24hour:Minute:Second
|  [0] {^LN-BEG}MON Day, ExYear 12hour:Minute:Second AMPM
|  [0] {^LN-BEG}ExYearExMonthExDay(?:T|  ?)Ex24hourExMinuteExSecond(?:[.,]Microseconds)?(?:\s*Zone offset)?
|  [0] {^LN-BEG}(?:Zone name )?(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] {^LN-BEG}(?:Zone offset )?(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] {^LN-BEG}TAI64N
|  [0] (?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] (?:DAY )?MON Day ExYear %k:Minute:Second(?:\.Microseconds)?
|  [0] Day(?P<_sep>[-/])Month(?P=_sep)(?:ExYear|ExYear2) %k:Minute:Second
|  [0] Day(?P<_sep>[-/])MON(?P=_sep)ExYear[ :]?24hour:Minute:Second(?:\.Microseconds)?(?: Zone offset)?
|  [0] Month/Day/ExYear:24hour:Minute:Second
|  [0] Month-Day-ExYear %k:Minute:Second(?:\.Microseconds)?
|  [0] Epoch
|  [0] {^LN-BEG}24hour:Minute:Second
|  [0] ^<Month/Day/ExYear2@24hour:Minute:Second>
|  [0] ExYear2ExMonthExDay  ?24hour:Minute:Second
|  [0] MON Day, ExYear 12hour:Minute:Second AMPM
|  [0] ^MON-Day-ExYear2 %k:Minute:Second
|  [0] ExYearExMonthExDay(?:T|  ?)Ex24hourExMinuteExSecond(?:[.,]Microseconds)?(?:\s*Zone offset)?
|  [0] (?:Zone name )?(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] (?:Zone offset )?(?:DAY )?MON Day %k:Minute:Second(?:\.Microseconds)?(?: ExYear)?
|  [0] TAI64N
`-

Lines: 282004 lines, 0 ignored, 890 matched, 281114 missed
[processed in 8.48 sec]

Missed line(s): too many to print.  Use --print-all-missed to print all 281114 lines

Die IP-Adressen oben sind nur ein kleiner Auszug (insgesamt wurden über 28000 Einträge gefunden). Als nächstes konfigurieren wir uns ein Jail. Dazu öffnest du die jail.local und hängst am Ende der Datei folgenden Inhalt an (natürlich ohne die 3 Punkte) und erstellst im Anschluss noch die passende Action

# nano /etc/fail2ban/jail.local

...
[adguardhome]
enabled = true
port = domain,domain-s
filter = adguardhome
logpath = /var/adguardhome/work/data/querylog.json
banaction = adguard-action
maxretry = 15
findtime = 600
bantime = 86400

# nano /etc/fail2ban/action.d/adguard-action.conf
[Definition]

actionstart = iptables -N f2b-adguard
              iptables -A f2b-adguard -j RETURN
              iptables -I FORWARD -p udp -m multiport --dports 53,853 -j f2b-adguard

actionstop = iptables -D FORWARD -p udp -m multiport --dports 53,853 -j f2b-adguard
             iptables -F f2b-adguard
             iptables -X f2b-adguard

actioncheck = iptables -n -L FORWARD | grep -q 'f2b-adguard[ \t]'

actionban = iptables -I f2b-adguard 1 -s <ip> -j DROP

actionunban = iptables -D f2b-adguard -s <ip> -j DROP

Wie du siehst, hat das Jail fast dieselbe Konfiguration wie für wplogin (lediglich die Ports, der Filter und der Pfad zur Logdatei sind anders. Anschließend starten wir den Dienst fail2ban neu.

# systemctl restart fail2ban.service
# systemctl status fail2ban.service
● fail2ban.service - Fail2Ban Service
     Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled; vendor preset: enabled)
     Active: active (running) since Sat 2022-02-12 10:31:38 CET; 2s ago
       Docs: man:fail2ban(1)
    Process: 864104 ExecStartPre=/bin/mkdir -p /run/fail2ban (code=exited, status=0/SUCCESS)
   Main PID: 864105 (fail2ban-server)
      Tasks: 9 (limit: 9510)
     Memory: 12.8M
        CPU: 171ms
     CGroup: /system.slice/fail2ban.service
             └─864105 /usr/bin/python3 /usr/bin/fail2ban-server -xf start

Ich weiß, alles in allem viel zu tun. Aber du kannst anschließend, also wenn der Kopf nicht mehr raucht, wieder ruhiger schlafen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert