Une configuration de fail2ban sur un serveur public🔗

Posted by Médéric Ribreux 🗓 In blog/ Sysadmin/

#debian #auto-hébergement #sysadmin #sécurité

Introduction

Dès que vous posez un serveur sur Internet, vous vous récupérez un paquet de requêtes frauduleuses. Par frauduleuses, j'entends une transmission de paquets destinés à récupérer un accès non autorisé ou de faire un Denial-Of-Service. On peut aussi le résumer à une attaque venue du réseau Internet car c'est bien de cela dont il s'agit.

Si vous analysez vos logs, vous verrez une tonne de lignes qui rejettent des accès sur vos différents services publics. Sur les systèmes sous GNU/Linux, votre distribution se doit de maintenir la sécurité en vous fournissant des mises à jour adaptées. Et heureusement… Il n'y a pas si longtemps, la faille shellshock est sortie et à peine quelques heures après, je pouvais lire des lignes d'attaques dans mes logs Apache. Je peux également repérer des attaques qui tentent d'exploiter des failles de sécurité sur des logiciels, par exemple, les attaques sur Wordpress sont légion. Je peux même affirmer que près de 90% des erreurs que je rencontre sur mon serveur web sont des tentatives d'accès frauduleux.

Il faut relever que ces attaques sont portées sur vos services publics pour lesquels vous avez une porte ouverte au sein de votre pare-feu. Même si vous disposez de règles de pare-feu bien ciselées, il y a de grandes chances que vous soyez vulnérables à toute attaque de type brute force password cracking. Même si votre distribution va s'occuper pour vous (enfin, ses contributeurs) de corriger les failles de sécurité rapidement, sachez que vous ne serez pas toujours à l'abri, même si on peut considérer dans la pratique que vous êtes couverts dans 99% des cas. C'est que tout le monde a pu constater sur la faille Shellshock où la publication des correctifs a pris près d'une semaine avant de couvrir complètement le problème.

Il faut donc se prémunir de ce genre d'attaque. Une parade simple et efficace consiste à repérer ses tentatives d'accès frauduleux et de couper purement et simplement l'accès des machines distantes à votre machine publique via le pare-feu. De plus, il est bon de déterminer la fréquence d'attaque. A moins de surveiller en permanence vos logs manuellement, c'est une information difficile à obtenir. De plus, des attaques peuvent cibler votre machine pendant un moment donné comme dans le cas d'un denial of service et il vous faudra être alerté de ce problème pour pouvoir réagir en conséquence.

Bien entendu, comme ce problème d'attaque est devenu le quotidien pour les machines exposées sur Internet, des outils ont été élaborés pour tenter de le gérer. Aujourd'hui, je vais exposer l'outil fail2ban qui permet de répondre, au moins en partie, aux tentatives d'accès frauduleux.

Concepts de fail2ban

Pour résumer, fail2ban est un analyseur de logs à actions. Globalement, il lit vos fichiers de logs et en fonction de leur contenu, il lance des actions. Ces actions sont généralement des choses comme des modifications de règles de pare-feu pour éviter que l'adresse IP distante puisse disposer d'un quelconque accès à votre serveur public.

Pour détecter une attaque dans les logs, fail2ban utilise des filtres. Il s'agit d'expressions régulières un peu spécifiques qui sont calquées sur les messages des logs. Elles permettent de remonter quel service est touché, quelle adresse IP tente un accès frauduleux, la date et l'heure, etc.

Quand fail2ban détecte une attaque, il lance des actions. Ces dernières sont définies dans des fichiers de configuration spécifiques. Elles peuvent se cumuler et prendre en compte des paramètres relevés dans les filtres (ou dans d'autres fichiers de configuration). L'action par défaut de fail2ban consiste à modifier les règles de pare-feu via iptables en interdisant à l'adresse IP de l'attaquant de se connecter sur le serveur requêté.

Bien entendu, il faut lier des filtres à des actions, car on ne va pas réagir de la même manière à une tentative de relais de spam sur un serveur SMTP qu'à un denial-of-service sur un serveur web. Ce regroupement s'appelle un "jail". La plupart du temps, on définit un jail par type de service (un pour sshd, un pour httpd). On peut également définir un jail spécifique pour isoler par exemple les tentatives d'accès frauduleux sur une authentification HTTP et un autre pour réagir aux attaques sur une partie spécifique d'un site web. Dans ce cas, on définira deux jails différents, car les filtres employés seront différents.

Petite précision pour terminer, fail2ban est écrit en Python. C'est un point intéressant car sur une machine aux performances limitées, le lancement de fail2ban aura une conséquence en termes de temps de traitement.

Spécificités de la configuration Debian

Par défaut, Debian livre des fichiers de configuration situés dans /etc/fail2ban/. Les filtres sont stockés dans des fichiers de configuration spécifiques hébergés dans /etc/fail2ban/filter.d/. Les actions sont rangées dans /etc/fail2ban/action.d/. Les jails sont définis dans un seul fichier de configuration nommé /etc/fail2ban/jail.conf. Enfin, il existe un fichier de configuration générale de fail2ban nommé /etc/fail2ban/fail2ban.conf.

Par ailleurs, les mainteneurs Debian du paquet fail2ban livrent un ensemble de fichiers de référence dont l'extension se termine par .conf. La recommandation est de ne pas toucher à ces fichiers et de construire votre propre configuration dans des fichiers dont l'extension se termine par .local. Ces fichiers se substituent aux fichiers .conf. Ainsi, si vous désirez configurer vos propres jails, il est recommandé de créer le fichier /etc/fail2ban/jail.local. Vous pouvez copier le contenu du fichier .conf qui correspond (jail.conf) et le customiser à votre sauce, seule votre configuration sera appliquée.

Vous pouvez faire la même chose pour les fichiers de filtre ou les fichiers d'action. Cette méthode élégante permet de customiser simplement fail2ban sans modifier la configuration des mainteneurs Debian du paquet.

Principes de constitution d'un jail fail2ban

Il est maintenant temps de visualiser comment se construit un jail fail2ban. Nous allons passer en revue un fichier de filtre, un fichier d'action et enfin la définition d'un jail.

Les filtres

Pour mieux comprendre, voici le contenu du fichier de filtre /etc/fail2ban/filter.d/sshd.conf:

# Fail2Ban configuration file
#
# Author: Cyril Jaquier
#
# $Revision$
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf

[Definition]
_daemon = sshd
# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = ^%(__prefix_line)s(?:error: PAM: )?Authentication failure for .* from <HOST>\s*$
		^%(__prefix_line)s(?:error: PAM: )?User not known to the underlying authentication module for .* from <HOST>\s*$
		^%(__prefix_line)sFailed (?:password|publickey) for .* from <HOST>(?: port \d*)?(?: ssh\d*)?$
		^%(__prefix_line)sROOT LOGIN REFUSED.* FROM <HOST>\s*$
		^%(__prefix_line)s[iI](?:llegal|nvalid) user .* from <HOST>\s*$
		^%(__prefix_line)sUser .+ from <HOST> not allowed because not listed in AllowUsers$
		^%(__prefix_line)sauthentication failure; logname=\S* uid=\S* euid=\S* tty=\S* ruser=\S* rhost=<HOST>(?:\s+user=.*)?\s*$
		^%(__prefix_line)srefused connect from \S+ \(<HOST>\)\s*$
		^%(__prefix_line)sAddress <HOST> .* POSSIBLE BREAK-IN ATTEMPT!*\s*$
		^%(__prefix_line)sUser .+ from <HOST> not allowed because none of user's groups are listed in AllowGroups\s*$

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex = 

On peut observer que la partie la plus importante du fichier de filtre est la définition de la variable failregex. C'est ici qu'est le cœur du filtre. La regexp est basée sur les expressions rationnelles Python et notamment sur les groupes de capture nommés. C'est le cas de tout ce qui est de la forme (?P…). Globalement, cette expression capture le contenu entre parenthèse et le place dans un groupe nommé name.

Intéressons-nous à l'expression %(__prefix_line)s. Cette dernière est une chaîne de caractères dont le contenu est dynamique. Ici, c'est la variable nommée __prefix_line qui sera affichée à la place de l'expression. Cette dernière est au format Python ConfigParser.

Pour comprendre la totalité de l'expression, il faut disposer du contenu du fichier common.conf qui définit un certains nombre de variables par défaut dont __prefixline. Une variable attire l'attention. Il s'agit de . Il est tellement courant de filtrer sur une adresse IP que fail2ban inclue cette variable dont la valeur contient en fait (?:::f{4,6}:)?(?P[\w\-.^]+), ce qui permet de "matcher" une adresse IPv4 (y compris si elle est incluse dans une IPv6) ainsi qu'un nom de machine (FQDN ou local).

Enfin, on peut voir que l'expression contient plusieurs expressions à la suite des autres. Cela signifie que toute expression qui sera vérifiée validera le filtre (et déclenchera l'action).

Les actions

Voyons maintenant le contenu d'un fichier d'action. Il s'agit de /etc/fail2ban/action.d/iptables-multiport.conf qui est le fichier d'action par défaut, comme nous le verrons plus tard.

# Fail2Ban configuration file
#
# Author: Cyril Jaquier
# Modified by Yaroslav Halchenko for multiport banning
# $Revision$
#

[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 <chain> -p <protocol> -m multiport --dports <port> -j fail2ban-<name>

# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop = iptables -D <chain> -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 <chain> | 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 = iptables -I fail2ban-<name> 1 -s <ip> -j DROP

# 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

[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
#
protocol = tcp

# Option: chain
# Notes specifies the iptables chain to which the fail2ban rules should be
# added
# Values: STRING Default: INPUT
chain = INPUT 

On commence par trouver la définition des actions-types. Ces dernières sont nommées actionstart, actionend, actionban, action. Voici leur signification:

Enfin, le fichier d'action contient des paramètres par défaut. Ici, il s'agit du nom de la chaîne iptables, du port, du protocole et de la chaîne d'entrée d'iptables. Ces variables peuvent être également utilisées dans le fichier de définition des jails.

Pour terminer le jail

Pour terminer, il faut relier les filtres aux actions. Étudions une petite partie du fichier /etc/fail2ban/jail.conf:

banaction = iptables-multiport

# Action shortcuts. To be used to define action parameter
# The simplest action to take: ban only
action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]

# ban & send an e-mail with whois report to the destemail.
action_mw = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
		  %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", protocol="%(protocol)s", chain="%(chain)s"]

# ban & send an e-mail with whois report and relevant log lines
# to the destemail.
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
		   %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]

# Choose default action. To change, just override value of 'action' with the
# interpolation to the chosen action shortcut (e.g. action_mw, action_mwl, etc) in jail.local
# globally (section [DEFAULT]) or per specific section
action = %(action_)s

#
# JAILS
#
[ssh]

enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 6 

[xinetd-fail]

enabled = false
filter = xinetd-fail
port = all
banaction = iptables-multiport-log
logpath = /var/log/daemon.log
maxretry = 2

#
# HTTP servers
#
[apache]

enabled = false
port = http,https
filter = apache-auth
logpath = /var/log/apache*/*error.log
maxretry = 6 

On rencontre d'abord la création de quelques alias pour les actions par défaut. On trouve ainsi la définition de la variable banaction qui est affectée à iptables-multiport que nous avons étudié plus haut.

Étudions la syntaxe du premier alias:

action_ = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]

On peut observer que l'alias définit une action qui consiste à lancer l'action banaction (qui contient en fait iptables-multiport) avec les paramètres d'action name, port, protocol et chain.

Suivent d'autres définitions d'alias d'action qui permettent de lancer iptables-multiport et également d'envoyer un courrier électronique via sendmail. C'est l'action sendmail-whois.conf (si l'on remplace les variables par le bon contenu).

Enfin, on trouve l'action par défaut: action = %(action_)s, qui consiste tout simplement à utiliser l'expression utilisée plus haut: iptables-multiport avec les bons arguments.

Ensuite, on rencontre trois définitions de jail différentes:

De cet exemple, on peut en déduire qu'un jail se définit de la manière suivante:

[nom du jail]

enabled = true ou false suivant si on active ce jail ou pas
port = liste de ports sur lesquels porte l'action. Cette variable est transmise
       au fichier d'action. 
filter = nom du filtre pour détecter l'attaque
logpath = chemin du fichier de log à surveiller pour ce jail.
maxretry = nombre minimum de "match" du filtre dans le fichier de log avant de
           déclencher le bannissement. 
action = le nom du fichier d'action à lancer pour ce jail. si cette variable
         n'est pas remplie, on utilise celle définie plus haut dans la
		 configuration générale. 
banaction = Dans notre cas, banaction est une variable utilisée pour définir la
            variable action. On peut donc l'utiliser ici, elle sera substituée
			lors de l'appel de la variable action.

On voit donc que créer un jail est assez simple. Attention aux variables par défaut qui sont déclarées en amont du fichier de jail.

Adapter fail2ban à votre configuration

Introduction

Pour ma part, je tiens à superviser et sécuriser l'ensemble de mes services publics. J'utilise la méthode suivante:

Pour ma part, voici la liste des services publics que j'héberge sur ma machine:

Pour la majorité des cas, je veux empêcher les tentatives d'accès frauduleux par brute-force (ou par erreur de saisie). Pour certains services, il faudra aller plus loin comme détecter les attaques sur services web ou les dénis de service.

Verrouiller SSH

Ce premier point est primordial: si un compte POSIX est découvert via un brute-force-cracking SSH, votre serveur est vraiment compromis. Détecter ce genre d'attaque est donc essentiel. C'est d'ailleurs le seul jail lancé par défaut lorsque vous installez fail2ban.

Dans ce cadre, vous avez juste à utiliser le filtre /etc/fail2ban/filter.d/sshd.conf que je vous invite à lire avant d'utiliser.

Voici le code du jail:

[ssh]
enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 3
action   = %(action_mws)s

Attaques sur l'authentification HTTP

Ici, on veut juste bannir les machines distantes qui tentent de faire du cassage en force brute sur des comptes présent sur une authentification HTTP. En règle générale, placer une authentification HTTP règle une grande partie des problèmes d'attaque Apache. Si vous avez une application qui nécessite une authentification qui tourne derrière un serveur web et dont vous ne savez pas quel est le niveau de sécurité du code qui la compose, je vous recommande de placer une authentification HTTP avant: ça évitera que quelqu'un puisse exploiter directement une faille de sécurité de l'application pour casser l'authentification. En effet, je fais plus confiance à Apache pour la sécurité d'une authentification HTTP qu'à un mécanisme inconnu d'une application PHP codée avec les pieds.

Dans notre cas, le filtre par défaut suffit largement. Il est situé dans /etc/fail2ban/filter.d/apache-auth.conf. Je vous invite à le lire pour mieux comprendre.

Voici le code du jail:

[apache-http-auth]
enabled  = true
port     = http,https
filter   = apache-auth
logpath  = /var/log/apache2/error.log
maxretry = 3
action   = %(action_mw)s 

Attaques de failles de sécurité sur le serveur Web

Un serveur web sur Internet se prend un maximum de choses dans la gueule ! Voici ce que j'ai pu découvrir en lisant mes logs d'erreur Apache:

La réponse est assez directe: toute tentative d'accès sur ce genre d'URLs conduit à un bannissement immédiat !

Voici le code du filtre que j'ai placé dans /etc/fail2ban/filter.d/apache-noscript.local:

# Fail2Ban configuration file
#
# Authors: Cyril Jacquier, Médéric Ribreux
#
# $Revision$
#
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = apache-common.conf

[Definition]

# Option: failregex
# Notes.: regex to match the password failure messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#

failregex = ^%(_apache_error_client)s (File does not exist|script not found or unable to stat): /\S*(\.php|\.asp|\.exe|\.pl)\s*$
		^%(_apache_error_client)s script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$
		^%(_apache_error_client)s (File does not exist|script not found or unable to stat): /\S*([mM]y[Aa]dmin|php|cgi-|administrator|w00t|vtigercrm|tmUnblock).*$
		^%(_apache_error_client)s client sent HTTP/1.1 request without hostname.*$

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex = 

Et voici le code du jail:

[apache-noscript]
enabled  = true
port     = http,https
filter   = apache-noscript
logpath  = /var/log/apache2/error.log
maxretry = 1
action   = %(action_mwl)s 

Tentatives de relais de spam et d'accès frauduleux sur MTA

Pour le relayage de spam, en règle générale, les fraudeurs font une seule tentative. Si le serveur ne permet pas le relais, ils s'arrêtent ou bien recommencent quelques heures plus tard. Dans ce cas, autant être impartial: toute tentative de relais de spam sera caution à bannissement !

D'autres attaques peuvent avoir lieu notamment des tentatives de confirmation d'adresse (VRFY) pour tenter de trouver des adresses e-mail, ou encore des rejets de connexion.

Voici le code du filtre à mettre dans /etc/fail2ban/filter.d/exim-norelay.local:

# Fail2Ban configuration file
#
# Author: Médéric Ribreux
#
# $Revision$
#
[Definition]

# Option: failregex
# Notes.: regex to match the password failures messages in the logfile. The
# host must be matched by a group named "host". The tag "<HOST>" can
# be used for standard IP/hostname matching and is only an alias for
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
# In versions >= 0.8.11 below strings defined in exim-common.conf
host_info = H=([\w.-]+ )?(\(\S+\) )?\[<HOST>\](:\d+)? (I=\[\S+\]:\d+ )?(U=\S+ )?(P=e?smtp )?
pid = ( \[\d+\])?

failregex = ^%(pid)s %(host_info)ssender verify fail for <\S+>: Unrouteable address\s*$
		^%(pid)s \S+ F=(<>|\S+@\S+) %(host_info)srejected by local_scan\(\): .{0,256}$
		^%(pid)s %(host_info)s.*(?:relay not permitted).*$
		^%(pid)s %(host_info)s.*rejected (EXPN|VRFY) root.*$
		^%(pid)s rejected EHLO from \[<HOST>\]: syntactically invalid argument\(s\): \(no argument given\).*$
		^%(pid)s.*rejected connection from H=\[<HOST>\].*$

# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
#
ignoreregex = 

Et voici le code du jail:

[exim-norelay]
enabled  = true
filter   = exim
port     = smtp,ssmtp
logpath  = /var/log/exim4/rejectlog
maxretry = 1

Tentatives d'accès frauduleux sur serveur IMAP

Mon serveur IMAP n'est pas public. Dans ce cadre, les seules attaques qui peuvent survenir viennent du LAN. En règle générale, un indicateur pertinent est celui d'une attaque en force brute sur un compte IMAP ou tenter d'accéder à un compte qui n'existe pas. Dans ce cas, le fichier de filtre par défaut de fail2ban joue pleinement son rôle (lisez-le et comparez-le à vos logs d'échec).

Voici le code du jail:

[dovecot]
enabled = true
port = imap
filter = dovecot
logpath = /var/log/mail.log
maxretry = 3
action = %(action_mw)s 

Tester les règles de fail2ban

Vous avez maintenant défini l'ensemble de vos jails, mais il reste encore à les tester. En effet, qui vous dit que vous n'avez pas fait d'erreur de saisie où que votre filtre est défectueux ?

fail2ban dispose d'une commande bien pratique qui vous permet de vérifier vos filtres. Il s'agit de fail2ban-regex. Son utilisation est assez simple:

$ fail2ban-regex nom_du_fichier_de_log nom_du_fichier_de_filtre

Pour tester notre filtre Apache no-script, on va utiliser la commande suivante (et je vous présente également une partie des résultats):

$ fail2ban-regex /var/log/apache2/error.log ./filter.d/apache-noscript.local

Results
=======

Failregex
|- Regular expressions:
|  [1] ^\[[^]]+\] \[error\] \[client <HOST>\] (File does not exist|script not found or unable to stat): /\S*(\.php|\.asp|\.exe|\.pl|\.cgi)\s*$
|  [2] ^\[[^]]+\] \[error\] \[client <HOST>\] script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$
|  [3] ^\[[^]]+\] \[error\] \[client <HOST>\] (File does not exist|script not found or unable to stat): /\S*([mM]y[Aa]dmin|php|cgi-).*$
|
`- Number of matches:
[1] 0 match(es)
[2] 5 match(es)
[3] 86 match(es)

On obtient alors les stats de chaque regexp sur le fichier de log. Une expression à zéro peut vous faire douter de sa syntaxe. Il faudra creuser dans le fichier de log pour voir si rien ne déclenche le filtre.

Une fois que vous avez mis en place des règles de filtre, je vous conseille d'industrialiser les tests avec un fichier log spécialement fait pour. Une ligne suffit pour remplir les conditions de chaque test (soit une ligne par possibilité de match par expression de filtre, ce qui peut faire beaucoup. Effectivement, un fichier de log de cet acabit est assez pénible à mettre en place. Mais c'est le gage d'avoir un test fiable pour éprouver la configuration de fail2ban. Car quoi de plus dommage que d'avoir un fail2ban en mode passoire ?

Améliorer le fonctionnement par défaut de fail2ban

Correction du problème de date dans les courriels

Par défaut, la version de Debian de fail2ban formate les dates d'envoi de courriel via la commande date. Si vous utilisez une locale différente de la locale C, votre date ne respecte pas les standards du courrier électronique et le mail reçu n'aura pas de date valide. La solution est assez triviale, il suffit de modifier l'appel à la commande dateen la préfixant avec LC_TIME=C. Cette correction a été effectuée en amont et elle est disponible sur la version 0.9 de fail2ban. Malheureusement, la version sous Debian stable de fail2ban est la version 0.8.6. Nous pouvons néanmoins corriger ce problème en portant la modification dans un fichier d'action local. Celui qui nous intéresse est /etc/fail2ban/action.d/sendmail-whois.local. Par ailleurs, je profite de ce fichier pour:

Voici le contenu de /etc/fail2ban/action.d/sendmail-whois.local:

# Fail2Ban configuration file
#
# Author: Médéric RIBREUX
#
# $Revision$
#

[Definition]

# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
	  We do not send anything: too much mail kill mail
# Values: CMD
#
actionstart =

# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop =

# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =

# 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 = printf %%b "Subject: [Fail2Ban] service <name>: <ip> bannie…
		Date: `LC_TIME=C date -u +"%%a, %%d %%h %%Y %%T +0000"`
		From: Fail2Ban <<sender>>
		To: <dest>\n
		Alerte !\n
		L'adresse IP <ip> a été bannie par Fail2ban après
		<failures> tentatives d'accès à <name>.\n\n
		Quelques informations sur cet hôte:\n
		`/usr/bin/whois <ip>`\n\n
		A+,\n
		Fail2Ban" | /usr/sbin/sendmail -f <sender> <dest>

# 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 =

[Init]

# Defaut name of the chain
#
name = default

# Destination/Addressee of the mail
#
dest = root

# Sender of the mail
#
sender = root 

IPv6

Bon, fail2ban, c'est bien mais ma machine dispose d'un accès IPv6. Certes, j'ai un ensemble de règles de pare-feu mais mes services publics sont également ouverts sur IPv6. Il me faut donc une configuration IPv6 pour fail2ban. Le problème c'est que la version de fail2ban de Debian stable (Wheezy au moment de la rédaction de cet article) ne le supporte pas. Il existe des patchs non officiels, mais je n'ai pas vraiment envie de les appliquer pour l'instant. On verra lors d'une migration vers Jessie (qui devrait intervenir dans moins de 6 mois maintenant)…

TO BE continuated !

Alertes par SMS

Vous pouvez vouloir vous alerter autrement que par courrier électronique. Dans le cadre d'attaques sérieuses, vous pouvez vouloir être alerté sur votre téléphone mobile. Mon opérateur mobile me permet d'envoyer gratuitement des SMS sur mon téléphone grâce à une API web activable grâce à des outils en ligne de commande tels que wget ou curl. Je vais donc utiliser cette API pour m'envoyer des SMS encas de ban SSH sur mon serveur (ce qui est le moins probable, car le service SSH n'est pas exposé sur Internet).

D'abord, commençons par le script d'envoi de SMS. Il est générique. Je le place dans /usr/local/sbin/notify_sms.sh avec des droits uniquement pour root !

#!/bin/sh
# Script to send SMS via Free Mobile SMSAPI

USER="********" # The username for the API
PASS="*****" # The API key
URI="https://smsapi.free-mobile.fr"                                                                                                                                                           

# We grab stdin and convert newline to %0d to have multiline SMS
MESSAGE=$(cat | sed ':a;N;$!ba;s/\n/ %0d/g')

STATUS=$(wget -q -S --no-check-certificate -O- "${URI}/sendmsg?user=$USER&pass=$PASS&msg=$MESSAGE" 2>&1 | grep "HTTP/" | awk '/^ HTTP/{print $2}')

if [ "$STATUS" -ne "200" ]; then
  echo "Error (${STATUS}) in sending an SMS on $URI…" | logger -p security.error -t SMS
  exit $STATUS
fi

echo "An SMS has been sent to $URI…" | logger -p security.notice -t SMS

exit 0

Ensuite, il reste à intégrer ce script dans une action fail2ban. C'est assez simple, je vais m'inspirer de ce que j'ai trouvé dans la partie sur sendmail. Voici le contenu du fichier /etc/fail2ban/action.d/sendsms.local:

# Fail2Ban configuration file
#
# Author: Médéric RIBREUX
#
# $Revision$
#

[Definition]
# Option: actionstart
# Notes.: command executed once at the start of Fail2Ban.
# Values: CMD
#
actionstart =

# Option: actionstop
# Notes.: command executed once at the end of Fail2Ban
# Values: CMD
#
actionstop =

# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#

actioncheck =
# 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 = printf %%b "Alerte Fail2Ban sur le service <name> : <ip> bannie…
					Alerte !
					L'adresse IP <ip> a été bannie par Fail2ban après
					<failures> tentatives d'accès à <name>.\n
					--
					Envoyé depuis `hostname`." | /usr/local/sbin/notify_sms.sh

# 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 =

[Init]
# Defaut name of the chain
#
name = default 

Ensuite, pour faciliter l'utilisation de cette action dans le jail, je définis une action via alias:

action_mws = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
		 %(mta)s-whois[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
		 sendsms[name=%(__name__)s, logpath=%(logpath)s, chain="%(chain)s"]# plus loin dans la définition d'un jail:
action = %(action_mws)s

Voilà, ça marche et c'est bien formaté !

Conclusion

Nous avons pu constater que fail2ban permet de détecter un certain nombre d'attaques et de réagir automatiquement en bannissant les attaquants pour une certaine durée. Sa configuration sous Debian est spécifique mais semble assez logique et élégante. Par exemple, nous avons pu régler quelques problèmes sur le formatage des courriels ainsi que créer nos propres filtres. Lors des prochaines mises à jour du paquet, vos corrections resteront disponibles. Fail2ban est également hautement personnalisable. On peut créer de nouveaux jails et rédiger des actions bien spécifiques comme celle qui consiste à envoyer un SMS en cas d'attaque.

Reste le point noir de la non-gestion d'IPv6 qui, en 2014, ne devrait plus être rencontrée. Il existe des patchs et des pull-requests sur le code de Fail2ban sur ce sujet, espérons qu'ils seront bientôt versés dans le code officiel et qu'ils deviendront accessibles à l'ensemble des personnes qui veulent savoir ce qui se passe sur leurs machines exposées sur Internet.