Selenith
Projets, mémos et infos diverses

Atomisation des SPAM : Hack de spf-policyd (postfix)

Publié le 31/10/2020

Administrer son serveur de messagerie, c'est cool. Se faire blinder ses boites mails de SPAM, ça l'est moins. Il existe plein de methodes subtiles et inefficaces pour bloquer les SPAM tout en laissant passer les mail légitimes. Nous allons voir qu'avec un simple Hack de spf-policyd, la subtilité c'est pour les faibles et les couards !

Boite aux lettres avec inscription no junk mail

Vous ne passerez paaaaaas !

Le contexte

Configurer un systeme anti-spam sous postfix est une vrai misère. Même s'il existe des tres bons tutos, ils impliquent tous de nombreuses étapes de configurations complexes. De plus, il est souvent nécessaire d'utiliser des services tiers pour qualifier le niveau de confiance du serveur émetteur.

La methode usuelle

L'idée est de se concentrer sur le SPF. Je pars du principe que si quelqu'un administre un serveur de mail il DOIT avoir correctement configuré au moins le champ SPF du DNS lié à son domaine. C'est ultra basique et indispensable pour envoyer des mail vers des adresses gmail ou outlook.

La verification du SPF consiste à dire à votre serveur mail que lorsqu'il reçoit un mail, il doit verifier si l'IP du serveur émetteur correspond à l'IP du serveur officiellement déclaré de ce domaine.

Cette vérification peut être effectuée par un utilitaire nommé spf-policyd. Le problème c'est que cet utilitaire fonctionne uniquement si le domaine indiqué comme etant celui de l'émetteur déclare effectivement un champ SPF dans son DNS. Dans le cas ou rien ne serait déclaré, spf-policyd laisse passer le mail. Un peu embettant non ?

Du coup il est normalement necessaire de coupler spf-policyd avec DKIM, DMARC et spamassasin pour etre certain de couvrir tous les cas possibles.

Le hack

L'idée simple est de modifier spf-policyd pour qu'il rejette les mails si le SPF de l'emetteur n'existe pas (résultat "None"). La fonction de base, qui rejette le mail si le SPF ne correspond pas (résultat "Fail") continuera de fonctionner normalement. On va simplement rendre spf-policyd un peut plus aggressif.

Sous Débian 10 et ubuntu 20.04, apres avoir installé et configuré le paquet postfix-policyd-spf-python, il faut éditer le fichier /usr/lib/python3/dist-packages/spf_engine/__init__.py

A la ligne 159, sous le bloc suivant :

        if mfrom_policy == 'SPF_Not_Pass':
            try:
                unused_results.remove('Fail')
                actions['reject'].append('Fail')
                unused_results.remove('Softfail')
                actions['reject'].append('Softfail')
                unused_results.remove('Neutral')
                actions['reject'].append('Neutral')
            except:
                if debugLevel >= 2: syslog.syslog('Configuration File parsing error: Mail_From_reject')

Ajouter ces lignes suivantes apres "exept :, if debug...":

        # Hack de selenith
        elif mfrom_policy == 'Cerbere':
            try:
                unused_results.remove('Fail')
                actions['reject'].append('Fail')
                unused_results.remove('Softfail')
                actions['reject'].append('Softfail')
                unused_results.remove('None')
                actions['reject'].append('None')
            except:
                if debugLevel >= 2: syslog.syslog('Configuration File parsing error: Mail_From_reject')

La section devrait donc ressembler à ceci :

 if scope == 'mfrom':
        mfrom_policy = configData.get('Mail_From_reject')
        if "@" in sender:
            sender_domain = sender.split('@', 1)[1]
        else:
            sender_domain = ''
        if spf.domainmatch(reject_domain_list, sender_domain):
            mfrom_policy = 'SPF_Not_Pass'
            local['local_mfrom'] = True
        if mfrom_policy == 'SPF_Not_Pass':
            try:
                unused_results.remove('Fail')
                actions['reject'].append('Fail')
                unused_results.remove('Softfail')
                actions['reject'].append('Softfail')
                unused_results.remove('Neutral')
                actions['reject'].append('Neutral')
            except:
                if debugLevel >= 2: syslog.syslog('Configuration File parsing error: Mail_From_reject')
        # Hack de selenith
        elif mfrom_policy == 'Cerbere':
            try:
                unused_results.remove('Fail')
                actions['reject'].append('Fail')
                unused_results.remove('Softfail')
                actions['reject'].append('Softfail')
                unused_results.remove('None')
                actions['reject'].append('None')
            except:
                if debugLevel >= 2: syslog.syslog('Configuration File parsing error: Mail_From_reject')
        elif mfrom_policy == 'Softfail':
            try:
                unused_results.remove('Fail')

Avec ce petit patch, il va être necessaire de mettre en liste blanche les domaines des serveurs mail émetteurs qu'on peut considérer comme sûrs, mais dont les champs SPF sont mal ou non configurés.

Pour cela on va éditer le fichier de config /etc/postfix-policyd-spf-python/policyd-spf.conf

Ajouter en fin de fichier les lignes suivantes :

# Domaines des adresses emails emetteurs autorisés (mfrom) 
# Valide seulement si le SPF du domaine est configuré. Si SPF : None => utiliser HELO_Whitelist
Domain_Whitelist = freetelecom.fr,linuxfr.org,outlook.com,sfr.fr,orange.fr,google.com
# Domaines des serveurs emetteurs autorisé (HELO)
HELO_Whitelist =   free.fr,freetelecom.fr,amazonses.com,gouv.fr,outlook.com,sfr.fr,orange.fr,google.com,linkedin.com

Et pour finir on active notre patch en modifiant la valeur Mail_From_reject comme ceci :

# Mail_From_reject = Fail
Mail_From_reject = Cerbere

En ayant appliqué le patch dans la section qu'on a nommé "Cerbere", on peut l'activer ou le désactiver à la volée en changeant la valeur de Mail_From_reject de Fail à Cerbere.

J'ai mis les principaux domaines avec lesquels j'ai déja rencontrés des soucis.

A vous de compléter ou de modifier avec les domaines que vous trouvez pertinent et dont les champs SPF sont mal configurés (oui c'est toi que je regarde, le site des impots).

Aller plus loin

Une technique avec de la regexp savoureuse pour surveiller les domaines bloqués par notre spf-policyd cerbère sous stéroides.