Journal Attaques par force brute sur un serveur de mail

42
25
juin
2023

Sommaire

Bonjour à tous.

J'ai finalement obtenu quelque chose d'acceptable contre les attaques par force brute sur mon serveur de mail. Je ne pense pas avoir grand chose à cacher (c.f. nothing to hide) et mes emails personnels sont apparemment très intéressants pour quelqu'un :-D. Je suis actuellement en train de tester cette solution.

Je la partage ici, pour aider ceux qui aiment les sets nftables.

Le contexte.

En résumé, depuis peu, je vois un réseau /24 entier, dans un pays très éloigné, se connecter à mon serveur de messagerie et essayer plusieurs utilisateurs/mots de passe. D'après les logs, je peux voir que l'attaque a manifestement une gestion dynamique des vitesses de connexion et une détection de blocage…

Mes principales préoccupations sont donc 1) de réduire le bruit dans mes logs et 2) d'empêcher le trafic inutile vers mon serveur de messagerie - réduisons également l'empreinte carbone de mon petit serveur de messagerie ;-).

Mon serveur de messagerie est un serveur personnel, pour moi et ma famille. Par conséquent, je n'utilise qu'un client mail pour envoyer les miens, par exemple, Evolution, Thunderbird ou l'excellent FairEmail sur Android.

Voici les solutions possibles que j'ai envisagées :

1 - Mettre en place un VPN, et accepter les connexions de Submission(s) pour les adresses IP VPN uniquement.
2 - Mettre sur liste blanche quelques adresses IP pour l'accès à Submission(s), par exemple en utilisant des bases de données GeoIP.
3 - Utiliser des ports différents - non standard - pour Submission(s) (sic).
4 - Mettre à jour dynamiquement le pare-feu pour permettre aux utilisateurs d'envoyer un courrier électronique.
5 - Utiliser fail2ban

L'une des exigences que je souhaitais était la simplicité et la transparence pour l'utilisateur final.

La solution 1 est probablement très sûre, mais nécessite une quantité de travail importante, et ne serait probablement pas conforme à DNSSEC, que j'utilise.

La solution 2 est également une contrainte importante, et je n'ai pas beaucoup de temps. De plus, j'ai tendance à utiliser des VPN sur mes appareils.

La solution 3 n'est qu'une question de temps avant que les attaques arrivent sur ces ports, surtout que j'utilise des records PTR pour indiquer aux clients emails quels ports utiliser. Comme on dit en Anglais, "Security by obsurity"…

Pour des raisons qui ne sont pas développées ici, je n'ai pas voulu utiliser fail2ban. Pour faire court, je veux tester la faisabilité et le flexibilité de nftables.

Finalement, grâce aux script "post-login" de Dovecot, j'ai trouvé un moyen d'implémenter la solution 4, qui fonctionne plutôt bien, jusqu'à présent.

Voici donc un petit résumé, pour ceux qui sont intéressés.

Étape 1: création d'une liste blanche et d'une liste noire avec nftables.

Bien sûr, le pare-feu contient une règle qui permet les connections déjà établies. Je montre ici, à titre d'exemple:

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;
        iif "lo" accept
        ct state vmap { invalid : drop, established : accept, related : accept }
        ...
    }
}

Liste blanche

Il est nécessaire que la "liste blanche" soit évaluée avant la liste noire dans le pare-feu.

table inet filter {

    set trusted_ipv4 {
        type ipv4_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    set trusted_ipv6 {
        type ipv6_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    chain input {

        ip saddr . tcp dport @trusted_ipv4 \
        ct state new counter accept \
        comment "Trusted IPv4 ssh connections"

        ip6 saddr . tcp dport @trusted_ipv6 \
        ct state new counter accept \
        comment "Trusted IPv6 ssh connections"
    }

}

Liste noire

table inet filter {

    set banned_ipv4 {
        type ipv4_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    set banned_ipv6 {
        type ipv6_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    chain input {

        ip saddr . tcp dport @banned_ipv4 \
        ct state new counter drop \
        comment "Banned IPv4 ssh connections"

        ip6 saddr . tcp dport @banned_ipv6 \
        ct state new counter drop \
        comment "Banned IPv6 ssh connections"
    }

}

Deuxième étape: Limiter les connexions submission(s)

Les connexions sur les ports de submission ne sont pas bloquées, mais limitées à un certain débit, et les connexions suivantes sont automatiquement bannies.

À noter également, ce fichier nftables doit également contenir la définition des sets dont il va modifier le contenu.

table inet filter {

    set banned_ipv4 {
        type ipv4_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    set banned_ipv6 {
        type ipv6_addr . inet_service
        flags dynamic,timeout
        timeout 1d
    }

    chain input {

        # Accept SMTP connections by default
        tcp dport smtp ct state new counter accept \
        comment "Email transfer agent (smtp)"

        # Limit new submission connections ala fail2ban
        meta nfproto ipv4 tcp dport submissions ct state new,untracked \
        limit rate over 3/minute add @banned_ipv4 { ip saddr . submissions }

        meta nfproto ipv6 tcp dport submissions ct state new,untracked \
        limit rate over 3/minute add @banned_ipv6 { ip6 saddr . submissions }

        meta nfproto ipv4 tcp dport submission ct state new,untracked \
        limit rate over 3/minute add @banned_ipv4 { ip saddr . submission }

        meta nfproto ipv6 tcp dport submission ct state new,untracked \
        limit rate over 3/minute add @banned_ipv6 { ip6 saddr . submission }

        tcp dport { submission, submissions } ct state new counter accept \
        comment "Email submission(s)"
    }

}

Troisième étape: liste blanche automatique

Une fois qu'un utilisateur est authentifié avec succès, son adresse IP est automatiquement mise sur liste blanche pour accéder aux ports de soumission (465/587). Cela se fait avec un simple script appelé par Dovecot, une fois que l'authentification passée:

#!/bin/sh

# This script dynamically update firewall to whitelist IP addresses,
# for authenticating on submission(s) ports.
# It is called by Dovecot after IMAP authentication succeed.

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

# The default duration to use for whitelisting
period="1d"

# List of ports to whitelist
ports="143 993 995 465 587"

# The firewall list to update
fw_set=""

if ipcalc-ng -s -c -4 "$IP"; then
    fw_set="trusted_ipv4"
elif ipcalc-ng -s -c -6 "$IP"; then
    fw_set="trusted_ipv6"
fi

# Submission(s) authentication is now allowed from these IPs.
# imap(S) and pop3s are whitelisted, bypassing subsequent firewall rules.
if [ "$fw_set" != "" ]; then

    for port in $ports; do
        nft add element inet filter "$fw_set" "{ $IP . $port timeout $period }"
    done

fi

exec "$@"

Persistence des listes

Avec un tout petit peu de travail, il est très facile de sauvegarder les listes, avec un service systemd dépendant de nftables, qui s'éxécute un script avant l'arret et après le démarrage, en utilisant ces commandes, par exemple:

Sauvegarde

nft list set inet filter banned_ipv4 >/var/lib/nftables/banned_ipv4.nft
nft list set inet filter banned_ipv6 >/var/lib/nftables/banned_ipv6.nft
nft list set inet filter trusted_ipv4 >/var/lib/nftables/trusted_ipv4.nft
nft list set inet filter trusted_ipv6 >/var/lib/nftables/trusted_ipv6.nft

Restauration

nft -f /var/lib/nftables/banned_ipv4.nft
nft -f /var/lib/nftables/banned_ipv6.nft
nft -f /var/lib/nftables/trusted_ipv4.nft
nft -f /var/lib/nftables/trusted_ipv6.nft

L'écriture de service systemd est laissé comme exercice pour le lecteur…

Quelques liens

Conclusion

Cette solution a été testée et semble très bien fonctionner, tant sur les ordinateurs de bureau que sur les clients mobiles, avec IPv4 et IPv6.

Elle fonctionne si bien que je pourrais, en théorie, supprimer la règle d'acceptation pour les ports de soumission. Honnêtement, je suis très tenté.

En espérant contribuer ainsi à la réduction de bruit généré sur internet, notamment par les "script kiddies"…

  • # Très intéressant

    Posté par  . Évalué à 6.

    et même, je dirais très élégant.

    Merci.

    Pour savoir ou il faut enregistrer le script post-login de Dovecot, j'ai regardé ici.

    "Si tous les cons volaient, il ferait nuit" F. Dard

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.