Introduction

Le boot, c'est parfois long. Souvent, on a pas que ça à faire ! C'est très vrai pour les ordinateurs portables ou les stations de travail. Mais d'une manière générale, y compris pour des grosses machines de production, j'aime bien les séquences de boot rapide. En effet, plus le boot est rapide et plus la remise en fonctionnement des services associés à la machine est rapide. Plus on gagne de secondes sur ce sujet et mieux c'est. De plus, avoir une séquence de boot rapide implique que vous ayez passé un peu de temps sur cette dernière. C'est généralement une bonne pratique d'administration système. Si vous n'avez installé que le strict nécéssaire, votre séquence de démarrage sera souvent assez rapide.

Néanmoins, il existe des outils techniques qui permettent de gagner de précieuses secondes. Le premier qui me vient à l'esprit est systemd qui permet d'accélérer la séquence de démarrage en parralélisant au maximum le lancement des services, y compris si le réseau n'est pas encore configuré. Sur mes stations de travail, systemd permet d'économiser entre 5 et 10 secondes.

L'autre possibilité est d'utiliser un disque SSD pour le système. C'est le cas d'une de mes stations de travail. Néanmoins pour tout un tas d'autres machines, ce n'est pas forcément possible. Par exemple, un serveur de récupération (c'est du green-IT) n'a pas besoin d'un disque SSD. Ou peut-être n'avez-vous tout simplement pas les moyens de vous offrir un SSD.

Optimiseur de système de fichiers

Pour les systèmes à disques durs classiques, on peut quand même gagner du temps en utilisant un optimiseur de système de fichiers. Il existe par exemple readahead qui fonctionne avec systemd. Sous GNU/Linux Debian Wheezy, il existe un paquet nommé readahead-fedora. Le principe de ce dernier est de lire des fichiers pour les placer en mémoire. readahead est prévu pour être lancé au démarrage de la machine: il utilise une liste de fichiers à lire en amont dans un ordre bien déterminé de manière à accélérer le temps de démarrage. En effet, si on concentre les opérations de lecture de fichiers dont on aura besoin dans tout le processus de démarrage, les accès disques seront beaucoup mieux optimisés.

Mais on peut encore aller plus loin. En effet,on peut agir au sein du système de fichiers. Si tous les fichiers à lire pour accélérer la séquence de démarrage sont placés au même endroit, les uns à la suite des autres, leur lecture pour les placer en mémoire sera beaucoup plus rapide, les têtes de lecture n'ayant pas à se déplacer de manière répartie. Il y a un paquet pour ça: e4rat. Voyons comment l'installer...

Principes d'e4rat

e4rat est un petit logiciel qui se charge de regrouper les fichiers préchargés du système dans un endroit donné, de manière linéaire. Il ne fonctionne que sur les systèmes ext4 mais, comme btrfs n'est pas encore implémenté en standard dans toutes les distributions (et qu'il n'est pas encore prêt pour la production), nous pouvons allègrement l'utiliser.

Son principe d'utilisation est finalement assez proche de celui de readahead à la différence qu'il n'est pas vraiment géré à la sauce Debian. Certes il existe un paquet debian sur le site web de l'utilitaire mais il n'est pas intégré à la distribution.

e4rat utilise 3 binaires différents:

  • e4rat-collect qui est un peu le readahead-collector: il fabrique une liste de fichiers ouverts de manière chronologique.
  • e4rat-realloc qui s'occupe de réallouer les fichiers au bon endroit.
  • enfin, on trouve e4rat-preload qui joue le rôle de readahead: ce binaire va charger les fichiers dans l'ordre requis.

Installation d'e4rat

Cette fois, apt-get ou aptitude ne peuvent pas nous aider. Il faut aller sur le site web du projet et télécharger le paquet debian qui va bien. Si vous utilisez autre chose que du x86 ou de l'amd64 (de l'armhf ou du mips), il faudra recompiler à la main !

 # dpkg -i e4rat_0.2.3_amd64.deb

Le paquet installe quelques binaires dans /sbin/ ainsi que les pages de manuel relatives.

Configuration d'e4rat

Pour la partie configuration, elle sort vraiment du lot Debian: il faut intervenir en amont du système et indiquer au noyau linux le nouveau nom du processus init. C'est une technique identique à celle de bootchart. Nous devrons donc gérer tout ça dans la configuration de GRUB2.

Pour résumer, voici ce que nous allons faire:

  • booter le système en ayant pris soin de mettre en processus init le binaire /sbin/e4rat-collect
  • après quelques vérifications, rebooter le système en mode autonome (single) pour effectuer le repositionnement des fichiers concernés.
  • enfin, il faudra indiquer dans la configuration de GRUB2 qu'on souhaite définitivement utiliser le binaire e4rat-preload en processus init pour tout le temps pré-charger les fichiers concernés.

Voici le déroulé de ces étapes.

  • Ajoutez les lignes suivantes dans le fichier /etc/grub.d/40_custom (à adapter en fonction de votre noyau):

    menuentry 'E4Rat collect Debian GNU/Linux,3.2.0-4-amd64' --class debian --class gnu-linux --class gnu --class os { insmod gzio insmod part_msdos insmod ext2 set root='(hd0,msdos5)' search --no-floppy --fs-uuid --set=root 9beb0590-6505-41af-8daa-cdf23a108473 echo 'Chargement de Linux 3.2.0-4-amd64 avec E4RAT collection...' linux /boot/vmlinuz-3.2.0-4-amd64 root=UUID=9beb0590-6505-41af-8daa-cdf23a108473 ro quiet init=/sbin/e4rat-collect echo 'Chargement du disque mémoire initial ...' initrd /boot/initrd.img-3.2.0-4-amd64 }

  • Lancer la commande '''update-grub2''' pour mettre à jour le menu GRUB. Lancer alors un reboot de la machine et prendre soin de choisir cette nouvelle entrée.

  • Le système va se lancer et créer une liste de fichiers à précharger en fonction de ce qui est lancé au niveau du système de fichiers. La liste des fichiers est stockée, par défaut, dans le fichier '''/var/lib/e4rat/startup.log'''.

  • N'hésitez pas à fouiller cette liste et à virer les fichiers qui ne sont pas importants. Pour ma part, j'ai supprimé toutes les entrées qui mobilisent des fichiers de thumbnails par exemple.

  • Une fois la vérification de ce fichier réalisée, il suffit de redémarrer le système en mode de dépannage (single). Lorsque le système vous donne la main (après avoir donné le mot de passe root), il suffit d'entrer la commande '''e4rat-realloc /var/lib/e4rat/startup.log'''.

  • Les fichiers concernés par la réallocation vont être alors déplacés de manière à minimiser leur temps d'accès. Une fois que e4rat-realloc a terminé son travail, il suffit de rebooter sur une instance normale du système (la première entrée dans le menu de GRUB2).

  • Enfin, contrairement à readahead, le pré-chargement des fichiers de e4rat est réalisé au niveau du processus init (readahead se base sur un script sysvinit). Il faut donc l'indiquer dans la configuration de GRUB2. Une méthode (un peu bourrine il faut bien le reconnaître), consiste à modifier le contenu d'une variable du fichier '''/etc/default/grub''':

    GRUB_CMDLINE_LINUX_DEFAULT="quiet init=/sbin/e4rat-preload"

  • Après un '''update-grub2''', à chaque reboot, le système va charger les fichiers et tout devrait automagiquement aller plus vite.

Bien entendu cette configuration fonctionne très bien avec systemd car elle est indépendante du système d'init de la distribution.

Conclusion

L'installation d'e4rat est un peu complexe. Sa configuration n'est pas vraiment dynamique: si vous installez de nouveaux paquets ou de nouvelles versions de paquets existants, il faudra recommencer l'étape de collecte des fichiers en bootant sur l'entrée GRUB E4Rat collect et relancer e4rat-realloc. Néanmoins sur un système en production où ces changements sont peu nombreux, c'est très adapté. Avec e4rat on peut gagner près de 5 à 10 secondes supplémentaires sur la séquence de démarrage. Donc, le jeu en vaut clairement la chandelle vu le faible temps d'administration système nécéssaire pour mettre en place la configuration (moins de 30 minutes).

Bien entendu, e4rat n'a pas vraiment d'intérêt sur des disques SSD qui n'ont pas les mêmes problèmes de latence que les disques durs classiques. C'est la même chose pour les machines virtuelles !

Pour ma part, je l'ai déployé sur des stations de travail et des ordinateurs portables avec beaucoup de succès. Je le recommande y compris pour les serveurs de production (physiques bien sûr) comme processus à la mise en production: sur un parc un peu fourni, on peut remettre en service avec un peu d'avance et minimiser les temps d'indisponibilité. Vos utilisateurs seront plus rapidement contents et vos administrateurs systèmes nerveux un peu moins longtemps lors des redémarrages.

Posted ven. 14 nov. 2014 19:45:00

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'expression 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<name>...). 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 __prefix_line. Une variable attire l'attention. Il s'agit de <HOST>. 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<host>[\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

  • 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 banissement d'une machine distante. Dans notre cas, on supprimer 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. Etudions 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.

Etudions 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 appache

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. Ca 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écéssite 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 email, 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] ^\span>^<span class="hl opt">+\] \[error\] \[client <HOST>\] (File does not exist|script not found or unable to stat): /\S*(\.php|\.asp|\.exe|\.pl|\.cgi)\s*$
|  [2] ^\span>^<span class="hl opt">+\] \[error\] \[client <HOST>\] script '/\S*(\.php|\.asp|\.exe|\.pl)\S*' not found or unable to stat\s*$
|  [3] ^\span>^<span class="hl opt">+\] \[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 accabit 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 formatte 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:

  • 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 formatté !

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 formattage des courriels ainsiq que créer nos propres filtres. Lors des prochaines mises à jour du paquet, vos corrections resteront disponibles. Fail2ban est également hautement customisable. 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.

Posted mer. 26 nov. 2014 19:24:00 Tags: