Une configuration de fail2ban sur un serveur public🔗
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
, 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:
actionstart
: Cette action est lancée au démarrage du jail (donc au démarrage de fail2ban). Dans notre cas, elle consiste à initialiser des chaînes de pare-feu spécifiques dans le cadre d'une future utilisation.actionstop
: Cette action est lancée lors de l'arrêt du jail (donc à l'arrêt de fail2ban). Dans notre cas, elle s'occupe de supprimer les chaînes de pare-feu spécifiquement créées pour fail2ban.actioncheck
: Cette action permet de vérifier que le jail est bien en fonction. Ici, on vérifie que les chaînes de pare-feu spécifiques à fail2ban existent.actionban
: C'est l'action principale de fail2ban. Ici, on demande à iptables de rejeter (DROP) tous les paquets de l'IP en faute.actionunban
: C'est l'action qui permet de supprimer le bannissement d'une machine distante. Dans notre cas, on supprime l'IP de la chaîne de blocage d'iptables.
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:
- une pour SSH,
- une pour les services xinetd
- la dernière pour Apache
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:
- D'abord, il faut lister l'ensemble des services exposés à Internet. Je fais ça de mémoire, ça me permet de voir ce qui est prioritaire par rapport au reste (si je l'ai oublié, c'est que ce n'est pas prioritaire).
- Ensuite, je regarde quels sont les services réseaux en écoute sur la machine à sécuriser. Cela permet de voir quels sont les services qui ont été oubliés.
- Enfin, il faut lire les logs des services précédemment listés pour repérer les attaques les plus courantes. C'est la partie la plus fastidieuse mais également la plus intéressante. Ça ne fait jamais de mal de se plonger dans les logs Cette partie permet de déterminer les règles de filtrage dont on a besoin.
- Pour terminer, il est bon de se plonger dans la lecture des fichiers de filtre livrés par défaut avec fail2ban pour éviter de réinventer la roue. En règle générale, les développeurs de fail2ban implémentent des expressions de filtre qui font référence dans le domaine, donc inutile de tenter de recréer difficilement ce qui existe déjà. En plus de ça, il y a de grandes chances que votre expression ne soit pas correcte. Autant utiliser ce qui est déjà testé et éprouvé. Néanmoins, ces fichiers de référence ne devraient pas vous empêcher de faire ce que bon vous semble avec fail2ban.
Pour ma part, voici la liste des services publics que j'héberge sur ma machine:
- 22/ssh sur le réseau local uniquement (restreint par règle iptables sur ipv4 et Ipv6).
- 25/smtp sur Internet. Il s'agit du MTA exim4.
- 53/dns sur le réseau local uniquement (restreint par règle iptables sur ipv4 et ipv6).
- 80/http sur Internet. Il s'agit d'Apache et de tout les modules qui vont avec.
- 143/imap sur le réseau local uniquement (restreint par règle iptables sur ipv4 et ipv6). Il s'agit de dovecot.
- 443/https sur Internet. Il s'agit d'Apache et de tout les modules qui vont avec. Dans mon cas, les services hébergés en HTTPS sont différents de ceux en HTTP.
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:
- des attaques sur des failles de routeur Cisco (tmUnblock.cgi).
- des attaques sur PhpMyAdmin (que je n'ai pas).
- des attaques sur CGI
- des attaques dédiées aux failles de IIS (que je n'aurai jamais).
- des clients qui ne balancent pas leur nom.
- des attaques sur vtigercrm
- etc.
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 date
en 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:
- Franciser le message du courrier électronique car après tout, c'est moi qui vais le lire !
- Supprimer les actions actionsstart et actionstop qui envoient un mail pour chacun des jails qui sont lancés par fail2ban (au démarrage et à la fin du service): je n'ai pas besoin de ces informations.
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.